├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── general-question.md └── workflows │ ├── codeql.yml │ ├── e2e-test.yml │ ├── go-test.yml │ ├── image.yml │ ├── notify-devrel.yml │ └── solidity-test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── build ├── apinode.Dockerfile ├── docker-compose-dev.yml ├── docker-compose.yml ├── prover.Dockerfile └── sequencer.Dockerfile ├── cmd ├── apinode │ ├── README.md │ └── main.go ├── prover │ └── main.go └── sequencer │ └── main.go ├── codecov.yml ├── datasource ├── README.md ├── clickhouse.go └── datasource.go ├── docs ├── ARCHITECTURE.md ├── BUILD-CIRCUIT.md ├── CONTRIBUTING.md ├── DEVELOPER_GUIDE.md ├── OPERATOR_GUIDE.md ├── QUICK_START.md ├── RUN-LOCALLY.md ├── arch_new.drawio ├── arch_new.png ├── architecture.drawio ├── architecture.png ├── coordinator.drawio ├── coordinator.png ├── geod.md ├── pebble.md ├── prover.drawio ├── prover.png ├── sequencer.drawio ├── sequencer.png ├── task_flow_diagram.drawio └── task_flow_diagram.png ├── e2e ├── e2e_test.go ├── init.go ├── services │ ├── chain.go │ ├── clickhouse.go │ ├── contractdeploy.go │ ├── ipfs.go │ └── vm.go ├── testdata │ ├── gnark.code │ ├── gnark.metadata │ ├── liveness.circuit │ ├── liveness.pk │ ├── movement.circuit │ └── movement.pk └── util.go ├── examples ├── README.md ├── halo2-circuit │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── contracts │ │ └── Halo2Dapp.sol │ ├── projectfile │ │ └── verifyonchain │ └── src │ │ ├── circuits │ │ ├── empty.rs │ │ ├── fibonacci.rs │ │ ├── function.rs │ │ ├── gadgets │ │ │ ├── is_zero.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── simple.rs │ │ ├── generator.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── opts.rs ├── risc0-circuit │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── contract │ │ ├── ControlID.sol │ │ ├── Groth16Verifier.sol │ │ ├── IRiscZeroVerifier.sol │ │ ├── Risc0Dapp.sol │ │ ├── RiscZeroGroth16Verifier.sol │ │ ├── SafeCast.sol │ │ ├── StructHash.sol │ │ └── Util.sol │ ├── method │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── guest │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ └── src │ │ │ └── lib.rs │ ├── projectfile │ │ └── verifyonchain │ ├── range-prover.json │ └── src │ │ ├── lib.rs │ │ ├── main.rs │ │ └── opts.rs └── zkwasm-circuit │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── circuit │ ├── README.md │ └── src │ │ └── add.ts │ ├── setup │ └── K18.params │ └── src │ ├── lib.rs │ ├── main.rs │ ├── opts.rs │ ├── prover │ ├── mod.rs │ └── verifier.rs │ └── types │ ├── instance.rs │ ├── mod.rs │ ├── params.rs │ ├── proof.rs │ └── vkey.rs ├── go.mod ├── go.sum ├── metrics └── metrics.go ├── monitor └── monitor.go ├── project ├── manager.go ├── project.go └── project_test.go ├── scripts ├── README.md ├── compress_file.py ├── init_risc0server.sql └── start_compress.sh ├── service ├── apinode │ ├── aggregator │ │ └── aggregator.go │ ├── api │ │ └── http.go │ ├── apinode.go │ ├── config │ │ └── config.go │ └── db │ │ ├── clickhouse.go │ │ ├── db.go │ │ └── sqlite.go ├── prover │ ├── api │ │ └── http.go │ ├── config │ │ └── config.go │ ├── db │ │ └── db.go │ └── prover.go └── sequencer │ ├── api │ └── http.go │ ├── config │ └── config.go │ ├── db │ └── db.go │ └── sequencer.go ├── smartcontracts ├── .eslintrc ├── .gitignore ├── .openzeppelin │ ├── unknown-4689.json │ └── unknown-4690.json ├── .prettierrc ├── README.md ├── contracts │ ├── GeodnetDapp.sol │ ├── LivenessVerifier.sol │ ├── MovementBatchVerifier.sol │ ├── MovementVerifier.sol │ ├── ProjectDevice.sol │ ├── ProjectRegistrar.sol │ ├── W3bstreamDebits.sol │ ├── W3bstreamMinter.sol │ ├── W3bstreamProject.sol │ ├── W3bstreamProjectReward.sol │ ├── W3bstreamProver.sol │ ├── W3bstreamRewardDistributor.sol │ ├── W3bstreamRouter.sol │ ├── W3bstreamTaskManager.sol │ ├── W3bstreamVMType.sol │ ├── interfaces │ │ ├── IDapp.sol │ │ ├── IIoIDProxy.sol │ │ ├── IRouter.sol │ │ ├── ISlasher.sol │ │ ├── IStakingHub.sol │ │ └── ITaskManager.sol │ └── test │ │ ├── MockDapp.sol │ │ ├── MockDappLiveness.sol │ │ ├── MockDappMovementBatch.sol │ │ ├── MockERC20.sol │ │ ├── MockIoID.sol │ │ ├── MockIoIDRegistry.sol │ │ └── MockProject.sol ├── deploy.sh ├── go │ ├── debits │ │ ├── debits.abi │ │ └── debits.go │ ├── ioid │ │ ├── ioid.abi │ │ └── ioid.go │ ├── ioidregistry │ │ ├── ioid_registry.abi │ │ └── ioid_registry.go │ ├── minter │ │ ├── minter.abi │ │ └── minter.go │ ├── mockdappmovementbatch │ │ ├── mock_dapp_movement_batch.abi │ │ └── mock_dapp_movement_batch.go │ ├── mockerc20 │ │ ├── mock_erc20.abi │ │ ├── mock_erc20.bin │ │ └── mock_erc20.go │ ├── mockioid │ │ ├── mock_ioid.abi │ │ └── mock_ioid.go │ ├── mockioidregistry │ │ ├── mock_ioid_registry.abi │ │ └── mock_ioid_registry.go │ ├── mockproject │ │ ├── mock_project.abi │ │ └── mock_project.go │ ├── project │ │ ├── project.abi │ │ └── project.go │ ├── projectdevice │ │ ├── project_device.abi │ │ └── project_device.go │ ├── projectregistrar │ │ ├── project_registrar.abi │ │ └── project_registrar.go │ ├── projectreward │ │ ├── project_reward.abi │ │ └── project_reward.go │ ├── prover │ │ ├── prover.abi │ │ └── prover.go │ ├── router │ │ ├── router.abi │ │ └── router.go │ └── taskmanager │ │ ├── task_manager.abi │ │ └── task_manager.go ├── hardhat.config.ts ├── package.json ├── scripts │ ├── deploy-device.ts │ ├── deploy-geod.ts │ ├── deploy.ts │ └── upgrade.ts ├── solhint.config.js ├── test │ ├── ProjectRegistrar.ts │ ├── W3bstreamDebits.ts │ ├── W3bstreamMinter.ts │ ├── W3bstreamProject.ts │ ├── W3bstreamProver.ts │ ├── W3bstreamRouter.ts │ └── W3bstreamTaskManager.ts ├── tsconfig.json └── yarn.lock ├── task ├── assigner │ └── assigner.go ├── processor │ └── processor.go └── task.go ├── template ├── conf-output-template │ ├── ethereumContract.json │ ├── stdout.json │ └── textile.json ├── contract │ ├── Store.abi │ ├── Store.sol │ ├── data.go │ └── powerc20 │ │ ├── PoWERC20.abi │ │ └── PoWERC20.go └── project_file │ ├── gnark_liveness │ ├── gnark_movement │ ├── halo2 │ ├── movement_batch │ ├── risc0 │ ├── wasm │ └── zkwasm ├── util ├── env │ └── env.go ├── filefetcher │ └── file.go └── ipfs │ ├── ipfs.go │ └── ipfs_test.go └── vm ├── payload.go ├── proto ├── gen.sh ├── vm.pb.go ├── vm.proto └── vm_grpc.pb.go └── vm.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] Short description of the problem" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Edit config '...' 16 | 2. Run '....' 17 | 3. Send message '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, could you add screenshots to help explain your problem? 25 | 26 | **Versions (please complete the following information):** 27 | - Node Version [e.g. 0.2.9] 28 | - OS Name and Version: [e.g. Mac OS 14.1.1] 29 | - Architecture: Mac M1 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] Short feature description" 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General question 3 | about: For General Questions About W3bstream Sprout 4 | title: "[General Question ]" 5 | labels: wontfix 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## For General Questions About W3bstream Sprout 11 | 12 | Please do not use GitHub issues for asking general questions about the W3bstream Sprout project. Instead, we encourage you to seek support and ask questions in one of the following platforms: 13 | 14 | ### Join Our Discord Community 15 | For real-time discussions and community support, join our Discord workspace. This is a great place to get quick help, discuss features, and connect with other community members: 16 | [Join W3bstream Sprout Discord](https://github.com/[YourRepo]/[YourProject]/wiki/Discord) 17 | 18 | ### Ask on Stack Overflow 19 | For more structured and detailed questions, consider using Stack Overflow. Many of W3bstream Sprout's core and expert developers prefer this platform for its non-realtime format, which encourages well-structured and comprehensive questions. 20 | Ask your question here: [Stack Overflow - W3bstream Sprout Tag](https://stackoverflow.com/questions/tagged/sprout) 21 | 22 | Remember, a good question on Stack Overflow typically requires some upfront effort. Check out Stack Overflow's guide on [how to ask a good question](https://stackoverflow.com/help/how-to-ask). 23 | 24 | ### Found a Bug or Have a Feature Request? 25 | If you believe you've found a bug or if you have a proposal for a new feature, please use the appropriate issue template to report it. 26 | 27 | Thank you for being a part of the W3bstream Sprout community! 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "develop", "pull-request" ] 17 | pull_request: 18 | branches: [ "develop", "pull-request" ] 19 | schedule: 20 | - cron: '27 20 * * 0' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners 29 | # Consider using larger runners for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 32 | permissions: 33 | # required for all workflows 34 | security-events: write 35 | 36 | # only required for workflows in private repositories 37 | actions: read 38 | contents: read 39 | 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | language: [ 'go', 'javascript-typescript' ] 44 | # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] 45 | # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both 46 | # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 47 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 48 | 49 | steps: 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | 53 | # Initializes the CodeQL tools for scanning. 54 | - name: Initialize CodeQL 55 | uses: github/codeql-action/init@v3 56 | with: 57 | languages: ${{ matrix.language }} 58 | # If you wish to specify custom queries, you can do so here or in a config file. 59 | # By default, queries listed here will override any specified in a config file. 60 | # Prefix the list here with "+" to use these queries and those in the config file. 61 | 62 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 63 | # queries: security-extended,security-and-quality 64 | 65 | 66 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). 67 | # If this step fails, then you should remove it and run the build manually (see below) 68 | - name: Autobuild 69 | uses: github/codeql-action/autobuild@v3 70 | 71 | # ℹ️ Command-line programs to run using the OS shell. 72 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 73 | 74 | # If the Autobuild fails above, remove it and uncomment the following three lines. 75 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 76 | 77 | # - run: | 78 | # echo "Run, Build Application using script" 79 | # ./location_of_script_within_repo/buildscript.sh 80 | 81 | - name: Perform CodeQL Analysis 82 | uses: github/codeql-action/analyze@v3 83 | with: 84 | category: "/language:${{matrix.language}}" 85 | -------------------------------------------------------------------------------- /.github/workflows/e2e-test.yml: -------------------------------------------------------------------------------- 1 | name: E2E tests 2 | 3 | on: 4 | push: 5 | branches: [ "develop"] 6 | pull_request: 7 | branches: [ "develop"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ ubuntu-latest ] 15 | permissions: 16 | contents: read 17 | packages: write 18 | id-token: write 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | ref: ${{ github.event.pull_request.head.sha }} 24 | fetch-depth: 0 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: '21.x' 30 | 31 | - name: Run e2e tests 32 | run: make e2e_test 33 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Go unit tests 2 | 3 | on: 4 | push: 5 | branches: [ "develop"] 6 | pull_request: 7 | branches: [ "develop"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ ubuntu-latest ] 15 | permissions: 16 | contents: read 17 | packages: write 18 | id-token: write 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | ref: ${{ github.event.pull_request.head.sha }} 24 | fetch-depth: 0 25 | 26 | - name: Setup Go 27 | uses: actions/setup-go@v3 28 | with: 29 | go-version: 1.22 30 | 31 | - name: Run unit tests. 32 | run: make unit_test 33 | 34 | - name: Upload Coverage report to CodeCov 35 | uses: codecov/codecov-action@v1.0.0 36 | with: 37 | token: ${{secrets.CODECOV_TOKEN}} 38 | file: ./cover.out 39 | -------------------------------------------------------------------------------- /.github/workflows/image.yml: -------------------------------------------------------------------------------- 1 | name: build images 2 | 3 | on: 4 | push: 5 | tags: [ "v*.*.*" ] 6 | workflow_dispatch: 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | 11 | jobs: 12 | build_image: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | name: [apinode, prover, sequencer] 18 | include: 19 | - name: apinode 20 | image_name: iotexproject/w3bstream-apinode 21 | dockerfile: build/apinode.Dockerfile 22 | - name: prover 23 | image_name: iotexproject/w3bstream-prover 24 | dockerfile: build/prover.Dockerfile 25 | - name: sequencer 26 | image_name: iotexproject/w3bstream-sequencer 27 | dockerfile: build/sequencer.Dockerfile 28 | 29 | permissions: 30 | contents: read 31 | packages: write 32 | id-token: write 33 | 34 | steps: 35 | - uses: actions/checkout@v3 36 | with: 37 | ref: ${{ github.event.pull_request.head.sha }} 38 | fetch-depth: 0 39 | 40 | - name: Setup Docker buildx 41 | uses: docker/setup-buildx-action@v2 42 | 43 | - name: Log into registry ${{ env.REGISTRY }} 44 | if: github.event_name != 'pull_request' 45 | uses: docker/login-action@v2 46 | with: 47 | registry: ${{ env.REGISTRY }} 48 | username: ${{ github.actor }} 49 | password: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | - name: Extract Docker metadata 52 | id: meta 53 | uses: docker/metadata-action@v5 54 | with: 55 | images: | 56 | ${{ env.REGISTRY }}/${{ matrix.image_name }} 57 | 58 | - name: Build and push Docker image ${{ matrix.name }} 59 | uses: docker/build-push-action@v6 60 | with: 61 | context: . 62 | platforms: linux/amd64, linux/arm64 63 | file: ./${{ matrix.dockerfile }} 64 | push: ${{ github.event_name != 'pull_request' }} 65 | tags: ${{ steps.meta.outputs.tags }} 66 | labels: ${{ steps.meta.outputs.labels }} 67 | outputs: type=image,name=${{ env.REGISTRY_IMAGE }},name-canonical=true 68 | -------------------------------------------------------------------------------- /.github/workflows/notify-devrel.yml: -------------------------------------------------------------------------------- 1 | name: DevRel Notification 2 | 3 | on: 4 | push: 5 | paths: 6 | - examples/** 7 | # - smartcontracts/** 8 | - docs/** 9 | pull_request: 10 | paths: 11 | - examples/** 12 | # - smartcontracts/** 13 | - docs/** 14 | 15 | jobs: 16 | notify: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v3 21 | 22 | - name: Create or Update Issue 23 | uses: actions/github-script@v6 24 | with: 25 | script: | 26 | const { owner, repo } = context.repo; 27 | 28 | const issueTitle = 'Checkout new changes in Examples or Docs'; 29 | 30 | // Determine if this was triggered by a push or pull request 31 | const isPR = !!context.payload.pull_request; 32 | const triggerLink = isPR 33 | ? context.payload.pull_request.html_url 34 | : `${context.payload.repository.html_url}/commit/${context.sha}`; 35 | 36 | const triggerDescription = isPR 37 | ? context.payload.pull_request.body || "No PR description provided." 38 | : context.payload.head_commit.message || "No commit message provided."; 39 | 40 | const triggerType = isPR ? "PR" : "commit"; 41 | const issueBody = `@simonerom Changes were detected in the 'docs', 'examples', or 'smartcontracts' folder. 42 | Please review the changes and update the docs portal if required. 43 | 44 | The changes were triggered by this ${triggerType}: 45 | 46 | [${triggerLink}](${triggerLink}) 47 | _${triggerDescription}_`; 48 | 49 | try { 50 | const issues = await github.rest.issues.listForRepo({ 51 | owner, 52 | repo, 53 | state: 'open', 54 | labels: 'notification' 55 | }); 56 | 57 | if (issues.data.length === 0) { 58 | // Create a new issue if no open issues with the label exist 59 | await github.rest.issues.create({ 60 | owner, 61 | repo, 62 | title: issueTitle, 63 | body: issueBody, 64 | labels: ['notification'] 65 | }); 66 | } else { 67 | // Comment on the first open issue with the label 68 | const issueNumber = issues.data[0].number; 69 | await github.rest.issues.createComment({ 70 | owner, 71 | repo, 72 | issue_number: issueNumber, 73 | body: issueBody 74 | }); 75 | } 76 | } catch (error) { 77 | console.error('Error creating or updating issue:', error); 78 | throw error; 79 | } 80 | env: 81 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} # Using PAT_TOKEN if needed for permissions 82 | -------------------------------------------------------------------------------- /.github/workflows/solidity-test.yml: -------------------------------------------------------------------------------- 1 | name: Solidity unit tests 2 | 3 | on: 4 | push: 5 | branches: [ "develop"] 6 | pull_request: 7 | branches: [ "develop"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ ubuntu-latest ] 15 | permissions: 16 | contents: read 17 | packages: write 18 | id-token: write 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | ref: ${{ github.event.pull_request.head.sha }} 24 | fetch-depth: 0 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: '21.x' 30 | 31 | - name: Run unit tests 32 | run: make contract-test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | postgres 24 | 25 | .env 26 | docker-compose.local.yaml 27 | docker-compose.local.yml 28 | docker-compose.debug.yaml 29 | docker-compose.debug.yml 30 | 31 | # rust 32 | examples/**/target/ 33 | examples/*/pkg/ 34 | 35 | .vscode 36 | bin 37 | 38 | local_db 39 | project_cache 40 | smartcontracts/contracts/artifacts/ 41 | smartcontracts/contracts/interfaces/artifacts/ 42 | smartcontracts/.deps 43 | 44 | # tmp 45 | clients/contracts/ioid 46 | node_modules 47 | 48 | examples/halo2-circuit/contracts/.*.sol 49 | bin 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | --- 4 | ## [0.12.0] - 2024-06-06 5 | 6 | ### Changed 7 | - Now dispatch window will perceive prover amount 8 | - The project & prover contract history data will store in local db 9 | 10 | --- 11 | ## [0.10.1] - 2024-04-26 12 | 13 | ### Changed 14 | - Use contract multi call to query block accurate prover&project contract data 15 | - Change block memeory snapshot logic, now scheduler data more determinate 16 | - Support "RequiredProverAmountHash" project attribute, now project can define required provers, and change it at running 17 | - All chain monitor logic summarized in one place, avoid data inconsistencies 18 | 19 | ### Added 20 | - task metrics 21 | 22 | --- 23 | ## [0.10.0] - 2024-04-12 24 | 25 | ### Changed 26 | - Support new project & prover manager contract 27 | - Support cache project & prover snapshot 28 | - When prover restart, will rebuild schedule status when start 29 | - Now scheduler more robust 30 | 31 | ### Added 32 | - Support load local project file 33 | 34 | --- 35 | ## [0.9.0] - 2024-04-07 36 | 37 | ### Added 38 | - Support task window 39 | - Support task timeout 40 | - Support task retry 41 | - Support retrive task within project 42 | 43 | --- 44 | ## [0.8.0] - 2024-03-25 45 | 46 | ### Added 47 | - Support prover scheduling 48 | - Support multi prover 49 | - Support wasm vm 50 | 51 | ### Changed 52 | - Rename "znode" to "prover" 53 | - Rename "enode" to "coordinator" 54 | - Rename "http_plugin" to "sequencer" 55 | 56 | --- 57 | ## [0.7.0] - 2024-03-15 58 | 59 | ### Added 60 | - Support output to textile 61 | - Add http plugin for message receive and pack 62 | - Support postgres data source 63 | 64 | ### Changed 65 | - Enode now support pull task from data source 66 | 67 | --- 68 | ## [0.6.4] - 2024-02-26 69 | 70 | ### Added 71 | - Sandbox contract support 72 | - More unit test 73 | 74 | ### Changed 75 | - Output support snark proof pack 76 | 77 | --- 78 | ## [0.6.1] - 2024-02-19 79 | 80 | ### Changed 81 | - When message have not packed to task, query message will return reveived state 82 | - Znode will auto join project p2p topic 83 | 84 | --- 85 | ## [0.6.0] - 2024-02-15 86 | 87 | ### Added 88 | - Support project define message aggregation strategy 89 | - Support did auth token 90 | 91 | --- 92 | ## [0.5.0] - 2023-12-28 93 | 94 | ### Added 95 | - Support project self-define contract abi and method 96 | - Support message raw data as output param 97 | 98 | ### Changed 99 | - Powerc20, add a router contract, and use contract address for challenge 100 | 101 | --- 102 | ## [0.4.1] - 2023-12-20 103 | 104 | ### Added 105 | - Support task 106 | - Support powerc20 miner 107 | 108 | --- 109 | ## [0.4.0] - 2023-12-07 110 | 111 | ### Added 112 | - Support risc0 local verify proof 113 | - Support halo2 local verify proof 114 | - Support zkwasm local verify proof 115 | - Support p2p network 116 | 117 | ### Changed 118 | - Update readme, replace Zero-Node with W3bstream 119 | 120 | --- 121 | ## [0.3.1] - 2023-11-30 122 | 123 | ### Added 124 | - Use ioctl control W3bstream sprout 125 | - Support Zkwasm 126 | - Support Halo2 VM 127 | - Support build circuit and circuit template 128 | - Support project config loading 129 | - Support ioctl ws query message state 130 | 131 | ### Changed 132 | - Refactor readme 133 | 134 | ### Fixed 135 | 136 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Variables 2 | BUILD_DIR=bin 3 | DOCKER_APINODE_TARGET=ghcr.io/iotexproject/w3bstream-apinode:latest 4 | DOCKER_PROVER_TARGET=ghcr.io/iotexproject/w3bstream-prover:latest 5 | DOCKER_BOOTNODE_TARGET=ghcr.io/iotexproject/w3bstream-bootnode:latest 6 | DOCKER_SEQUENCER_TARGET=ghcr.io/iotexproject/w3bstream-sequencer:latest 7 | 8 | # Build targets 9 | .PHONY: build 10 | build: build-apinode build-prover build-bootnode build-sequencer 11 | 12 | .PHONY: build-apinode 13 | build-apinode: 14 | go build -o $(BUILD_DIR)/apinode cmd/apinode/main.go 15 | 16 | .PHONY: build-prover 17 | build-prover: 18 | go build -o $(BUILD_DIR)/prover cmd/prover/main.go 19 | 20 | .PHONY: build-bootnode 21 | build-bootnode: 22 | go build -o $(BUILD_DIR)/bootnode cmd/bootnode/main.go 23 | 24 | .PHONY: build-sequencer 25 | build-sequencer: 26 | go build -o $(BUILD_DIR)/sequencer cmd/sequencer/main.go 27 | 28 | # Docker targets 29 | .PHONY: images 30 | images: 31 | docker build -f build/apinode.Dockerfile -t $(DOCKER_APINODE_TARGET) . 32 | docker build -f build/prover.Dockerfile -t $(DOCKER_PROVER_TARGET) . 33 | docker build -f build/bootnode.Dockerfile -t $(DOCKER_BOOTNODE_TARGET) . 34 | docker build -f build/sequencer.Dockerfile -t $(DOCKER_SEQUENCER_TARGET) . 35 | 36 | .PHONY: unit_test 37 | unit_test: 38 | GOARCH=amd64 go test -gcflags="all=-N -l" ./... -covermode=atomic -coverprofile cover.out 39 | 40 | # Clean targets 41 | .PHONY: clean 42 | clean: 43 | @rm -rf $(BUILD_DIR) 44 | 45 | .PHONY: contract-test 46 | contract-test: 47 | @cd smartcontracts && npm install --save-dev hardhat 48 | @cd smartcontracts && npx hardhat test 49 | 50 | .PHONY: e2e_test 51 | e2e_test: 52 | @cd smartcontracts && yarn install 53 | @TEST_E2E=true go test ./e2e -v 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # W3bstream 2 | 3 | ## Overview 4 | 5 | W3bstream is a key Layer-2 solution within the IoTeX ecosystem, designed to support verifiable data processing for Decentralized Physical Infrastructure Networks (*DePINs*). It operates through a network of *Sequencer nodes* and *Prover nodes*. 6 | 7 |

8 | 9 |

10 | 11 | **Sequencer nodes** assemble received data messages with a “block header” that (among other things) references the previous block of data. Each block of data is mined using a proof-of-work mechanism and is then assigned as a Task to a Prover node that is available for data computation. Sequencer nodes receive rewards in IOTX for the mining activity. 12 | 13 | **Prover nodes** fetch their tasks and process the task data to generate Zero-Knowledge (ZK) proofs, using circuits that are specific to the DePIN project associated with the data. Prover nodes will receive rewards denominated in the token of the DePIN project for which the proof was generated. 14 | 15 | The chain of tasks and their ZK-proofs are recorded **on the IoTeX blockchain**, making them accessible for dApps. The actual data, uniquely referenced by the on-chain tasks, remains available for full off-chain verification. 16 | 17 | This architecture ensures secure, reliable, and scalable data processing, allowing DePIN dApps to act on verified real-world facts to trigger blockchain-based incentives. 18 | 19 | 20 | ## Getting Started 21 | 22 | ### For Embedded Developers 23 | 24 | [ioID-SDK](https://github.com/iotexproject/ioID-SDK) repo provides SDK for DePIN hardwares to connect W3bstream and IoTeX ecosystem 25 | 26 | 27 | ### For Project Builders 28 | 29 | [Build a custom zk prover for W3bstream](./docs/DEVELOPER_GUIDE.md) 30 | 31 | 32 | [Deploy the zk prover to W3bstream](./docs/QUICK_START.md) 33 | 34 | ### For Node Operators 35 | 36 | 37 | > ⓘ **Note**: Joining the W3bstream network as a sequencer or prover node is currently unavailable. Stay tuned for updates in future releases. [Follow us on X](https://x.com/iotex_dev). 38 | 39 | ### For Module Integrators 40 | 41 | - Storage Modules: The [./datasource](./datasource/) folder contains [documentation](./datasource/README.md) and existing implementations for data storage that W3bstream can support. 42 | 43 | - ZK Engine Modules: To add a new type of ZK prover, please ensure the service implements the [Protobuf interface](./vm/proto/vm.proto) to enable communication with the W3bstream ZK Node. 44 | 45 | 46 | ## Contributing 47 | 48 | We welcome contributions! 49 | 50 | Please read our [contributing guidelines](./docs/CONTRIBUTING.md) and submit pull requests to our GitHub repository. 51 | -------------------------------------------------------------------------------- /build/apinode.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | 3 | ENV GO111MODULE=on 4 | ENV CGO_ENABLED=1 5 | 6 | WORKDIR /go/src 7 | COPY ./ ./ 8 | 9 | RUN apk add --no-cache gcc musl-dev 10 | 11 | RUN cd ./cmd/apinode && go build -o apinode 12 | 13 | FROM alpine:3.20 AS runtime 14 | 15 | ENV LANG en_US.UTF-8 16 | 17 | RUN apk add --no-cache ca-certificates tzdata 18 | 19 | COPY --from=builder /go/src/cmd/apinode/apinode /go/bin/apinode 20 | EXPOSE 9000 21 | 22 | WORKDIR /go/bin 23 | ENTRYPOINT ["/go/bin/apinode"] 24 | -------------------------------------------------------------------------------- /build/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: "3.6" 2 | 3 | services: 4 | bootnode: 5 | image: ghcr.io/iotexproject/w3bstream-bootnode:v0.16.19 6 | container_name: w3bstream-bootnode 7 | platform: linux/x86_64 8 | restart: always 9 | ports: 10 | - 8000:8000 11 | 12 | halo2: 13 | image: ghcr.io/iotexproject/halo2server:v0.0.7 14 | container_name: halo2-service 15 | platform: linux/x86_64 16 | restart: always 17 | ports: 18 | - 4002:4001 19 | 20 | zkwasm: 21 | image: wangweixiaohao2944/zkwasmserver:v0.0.6 22 | container_name: zkwasm-service 23 | platform: linux/x86_64 24 | restart: always 25 | ports: 26 | - 4003:4001 27 | 28 | risc0: 29 | image: ghcr.io/iotexproject/risc0server:v0.0.7 30 | depends_on: 31 | - "postgres" 32 | container_name: risc0-service 33 | platform: linux/x86_64 34 | restart: always 35 | environment: 36 | DATABASE_URL: postgres://test_user:test_passwd@postgres:5432/test?sslmode=disable 37 | BONSAI_URL: https://api.bonsai.xyz 38 | BONSAI_KEY: "${BONSAI_KEY:-}" 39 | ports: 40 | - "4001:4001" 41 | 42 | wasm: 43 | image: ghcr.io/iotexproject/wasmserver:v0.0.7 44 | container_name: wasm-service 45 | platform: linux/x86_64 46 | restart: always 47 | ports: 48 | - "4004:4001" 49 | 50 | postgres: 51 | image: postgres:14 52 | container_name: w3bstream-postgres 53 | restart: always 54 | command: 55 | [ 56 | "postgres", 57 | "-cshared_preload_libraries=pg_stat_statements" 58 | ] 59 | environment: 60 | POSTGRES_USER: test_user 61 | POSTGRES_PASSWORD: test_passwd 62 | POSTGRES_DB: test 63 | volumes: 64 | - ./postgres:/var/lib/postgresql/data 65 | ports: 66 | - "5432:5432" -------------------------------------------------------------------------------- /build/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | bootnode: 3 | image: ghcr.io/iotexproject/w3bstream-bootnode:latest 4 | container_name: w3bstream-bootnode 5 | restart: always 6 | build: 7 | context: . 8 | dockerfile: bootnode.Dockerfile 9 | ports: 10 | - "8000:8000" 11 | 12 | sequencer: 13 | image: ghcr.io/iotexproject/w3bstream-sequencer:latest 14 | depends_on: 15 | - "postgres" 16 | container_name: w3bstream-sequencer 17 | restart: always 18 | build: 19 | context: . 20 | dockerfile: sequencer.Dockerfile 21 | environment: 22 | - OPERATOR_PRIVATE_KEY=${OPERATOR_PRIVATE_KEY} 23 | ports: 24 | - "9000:9000" 25 | command: [ "-coordinatorAddress", "-databaseDSN", "postgres://postgres:mysecretpassword@postgres:5432/w3bstream?sslmode=disable" ] 26 | 27 | prover: 28 | image: ghcr.io/iotexproject/w3bstream-prover:latest 29 | depends_on: 30 | - "risc0" 31 | - "halo2" 32 | - "zkwasm" 33 | - "wasm" 34 | - "postgres" 35 | - "bootnode" 36 | container_name: w3bstream-prover 37 | platform: linux/x86_64 38 | restart: always 39 | build: 40 | context: . 41 | dockerfile: prover.Dockerfile 42 | environment: 43 | PROVER_ENV: PROD 44 | PROJECT_FILE_DIRECTORY: "/data" 45 | BOOTNODE_MULTIADDR: "/dns4/bootnode/tcp/8000/p2p/12D3KooWJkfxZL1dx74yM1afWof6ka4uW5jMsoGasCSBwGyCUJML" 46 | volumes: 47 | - ./test/project:/data 48 | 49 | halo2: 50 | image: ghcr.io/iotexproject/halo2server:v0.0.7 51 | container_name: halo2-service 52 | platform: linux/x86_64 53 | restart: always 54 | 55 | zkwasm: 56 | image: wangweixiaohao2944/zkwasmserver:v0.0.6 57 | container_name: zkwasm-service 58 | platform: linux/x86_64 59 | restart: always 60 | 61 | risc0: 62 | image: ghcr.io/iotexproject/risc0server:latest 63 | depends_on: 64 | - "postgres" 65 | container_name: risc0-service 66 | restart: always 67 | environment: 68 | DATABASE_URL: postgres://postgres:mysecretpassword@postgres:5432/w3bstream?sslmode=disable 69 | BONSAI_URL: https://api.bonsai.xyz 70 | BONSAI_KEY: "${BONSAI_KEY:-}" 71 | 72 | wasm: 73 | image: ghcr.io/iotexproject/wasmserver:v0.0.7 74 | container_name: wasm-service 75 | platform: linux/x86_64 76 | restart: always 77 | 78 | postgres: 79 | image: postgres:14 80 | container_name: w3bstream-postgres 81 | restart: always 82 | command: 83 | [ 84 | "postgres", 85 | "-cshared_preload_libraries=pg_stat_statements" 86 | ] 87 | environment: 88 | POSTGRES_USER: postgres 89 | POSTGRES_PASSWORD: mysecretpassword 90 | POSTGRES_DB: w3bstream 91 | volumes: 92 | - postgres_data:/var/lib/postgresql/data 93 | 94 | volumes: 95 | postgres_data: 96 | -------------------------------------------------------------------------------- /build/prover.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | 3 | ENV GO111MODULE=on 4 | ENV CGO_ENABLED=1 5 | 6 | WORKDIR /go/src 7 | COPY ./ ./ 8 | 9 | RUN apk add --no-cache gcc musl-dev 10 | 11 | RUN cd ./cmd/prover && go build -o prover 12 | 13 | FROM alpine:3.20 AS runtime 14 | 15 | RUN apk add --no-cache ca-certificates 16 | 17 | COPY --from=builder /go/src/cmd/prover/prover /go/bin/prover 18 | 19 | WORKDIR /go/bin 20 | ENTRYPOINT ["/go/bin/prover"] 21 | -------------------------------------------------------------------------------- /build/sequencer.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | 3 | ENV GO111MODULE=on 4 | ENV CGO_ENABLED=1 5 | 6 | WORKDIR /go/src 7 | COPY ./ ./ 8 | 9 | RUN apk add --no-cache gcc musl-dev 10 | 11 | RUN cd ./cmd/sequencer && go build -o sequencer 12 | 13 | FROM alpine:3.20 AS runtime 14 | 15 | RUN apk add --no-cache ca-certificates 16 | 17 | COPY --from=builder /go/src/cmd/sequencer/sequencer /go/bin/sequencer 18 | 19 | WORKDIR /go/bin 20 | ENTRYPOINT ["/go/bin/sequencer"] 21 | -------------------------------------------------------------------------------- /cmd/apinode/README.md: -------------------------------------------------------------------------------- 1 | # IoTeX DePIN Sequencer 2 | A reference implementation for A DePIN Sequencer service that supports: 3 | - W3bstream Tasks 4 | - Postres as the destination DA infra 5 | - Message aggregation 6 | - ioID Device Authentication 7 | - Secure device communication based on DID 8 | 9 | ## Example 10 | See https://github.com/iotexproject/w3bstream/blob/develop/docs/QUICK_START.md#interacting-with-w3bstream 11 | 12 | -------------------------------------------------------------------------------- /cmd/apinode/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "log/slog" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/pkg/errors" 11 | 12 | "github.com/iotexproject/w3bstream/service/apinode" 13 | "github.com/iotexproject/w3bstream/service/apinode/config" 14 | "github.com/iotexproject/w3bstream/service/apinode/db" 15 | ) 16 | 17 | func main() { 18 | cfg, err := config.Get() 19 | if err != nil { 20 | log.Fatal(errors.Wrap(err, "failed to get config")) 21 | } 22 | cfg.Print() 23 | slog.Info("apinode config loaded") 24 | 25 | db, err := db.New(cfg.LocalDBDir, cfg.DatabaseDSN) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | apinode := apinode.NewAPINode(cfg, db) 31 | 32 | if err := apinode.Start(); err != nil { 33 | log.Fatal(err) 34 | } 35 | defer apinode.Stop() 36 | 37 | done := make(chan os.Signal, 1) 38 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) 39 | <-done 40 | } 41 | -------------------------------------------------------------------------------- /cmd/prover/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "log/slog" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "github.com/pkg/errors" 12 | 13 | "github.com/iotexproject/w3bstream/service/prover" 14 | "github.com/iotexproject/w3bstream/service/prover/config" 15 | "github.com/iotexproject/w3bstream/service/prover/db" 16 | ) 17 | 18 | func main() { 19 | cfg, err := config.Get() 20 | if err != nil { 21 | log.Fatal(errors.Wrap(err, "failed to get config")) 22 | } 23 | cfg.Print() 24 | slog.Info("prover config loaded") 25 | 26 | prv, err := crypto.HexToECDSA(cfg.ProverPrvKey) 27 | if err != nil { 28 | log.Fatal(errors.Wrap(err, "failed to parse prover private key")) 29 | } 30 | proverOperatorAddress := crypto.PubkeyToAddress(prv.PublicKey) 31 | slog.Info("my prover", "address", proverOperatorAddress.String()) 32 | 33 | db, err := db.New(cfg.LocalDBDir, proverOperatorAddress) 34 | if err != nil { 35 | log.Fatal(errors.Wrap(err, "failed to new db")) 36 | } 37 | 38 | prover := prover.NewProver(cfg, db, prv) 39 | if err := prover.Start(); err != nil { 40 | log.Fatal(errors.Wrap(err, "failed to start prover")) 41 | } 42 | defer func() { 43 | if err := prover.Stop(); err != nil { 44 | log.Fatal(errors.Wrap(err, "failed to stop prover")) 45 | } 46 | }() 47 | 48 | done := make(chan os.Signal, 1) 49 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) 50 | <-done 51 | } 52 | -------------------------------------------------------------------------------- /cmd/sequencer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "log/slog" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "github.com/pkg/errors" 12 | 13 | "github.com/iotexproject/w3bstream/service/sequencer" 14 | "github.com/iotexproject/w3bstream/service/sequencer/config" 15 | "github.com/iotexproject/w3bstream/service/sequencer/db" 16 | ) 17 | 18 | func main() { 19 | cfg, err := config.Get() 20 | if err != nil { 21 | log.Fatal(errors.Wrap(err, "failed to get config")) 22 | } 23 | cfg.Print() 24 | slog.Info("sequencer config loaded") 25 | 26 | prv, err := crypto.HexToECDSA(cfg.OperatorPrvKey) 27 | if err != nil { 28 | log.Fatal(errors.Wrap(err, "failed to parse private key")) 29 | } 30 | 31 | db, err := db.New(cfg.LocalDBDir) 32 | if err != nil { 33 | log.Fatal(errors.Wrap(err, "failed to new db")) 34 | } 35 | 36 | s := sequencer.NewSequencer(cfg, db, prv) 37 | if err := s.Start(); err != nil { 38 | log.Fatal(errors.Wrap(err, "failed to start sequencer")) 39 | } 40 | defer func() { 41 | if err := s.Stop(); err != nil { 42 | log.Fatal(errors.Wrap(err, "failed to stop sequencer")) 43 | } 44 | }() 45 | 46 | done := make(chan os.Signal, 1) 47 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) 48 | <-done 49 | } 50 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | # only stat pull request 4 | patch: 5 | default: 6 | ## need patch coverage higher than 70% then success 7 | target: 70% 8 | ## allow the coverage rate decline 10% 9 | threshold: 10% 10 | ## full coverage should more than 50% 11 | range: 12 | 50..100 13 | ## ignore packages 14 | ignore: 15 | - scripts 16 | - examples 17 | # generated proto 18 | - vm/proto 19 | # generated contracts 20 | - smartcontracts 21 | # metrics 22 | - metrics 23 | # main 24 | - cmd/* 25 | - project/project_easyjson.go 26 | -------------------------------------------------------------------------------- /datasource/README.md: -------------------------------------------------------------------------------- 1 | # W3bstream Supported Data Availability (DA) Infra 2 | This folder contains the necessary components and interfaces to support DA infrastructure in W3bstream. 3 | 4 | ## Contents 5 | 6 | - **[DA Interface](./datasource.go):** Defines the interface for Data Availability implementations. 7 | - **[Example Implementation (Postgres)](./postgres.go):** A sample implementation of the DA interface using Postgres. 8 | 9 | ## Contributing 10 | 11 | We welcome contributions to expand the range of Data Availability implementations supported by W3bstream. If you have developed a new implementation, please follow these steps to contribute: 12 | 13 | 1. **Fork the Repository**: Create a personal copy of the repository by forking it. 14 | 2. **Create a Branch**: Create a new branch for your implementation. 15 | 3. **Implement and Test**: Develop your implementation and ensure it passes all tests. 16 | 4. **Submit a Pull Request (PR)**: Once your implementation is complete, submit a PR to the main repository. 17 | 18 | We will review your PR and merge it upon approval. Thank you for contributing to W3bstream! 19 | 20 | ## Getting Started 21 | 22 | To get started with the existing implementations, you can explore the [`datasource.go`](./datasource.co) and [`postgres.go`](./postgres.go) files for interface definitions and examples. 23 | 24 | ## Contact 25 | 26 | For any questions or support, please reach out to our team on [Discord](https://iotex.io/devdiscord) or open an issue in this repository. 27 | 28 | -------------------------------------------------------------------------------- /datasource/clickhouse.go: -------------------------------------------------------------------------------- 1 | package datasource 2 | 3 | import ( 4 | "context" 5 | "math/big" 6 | 7 | "github.com/ClickHouse/clickhouse-go/v2" 8 | "github.com/ClickHouse/clickhouse-go/v2/lib/driver" 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | "github.com/pkg/errors" 12 | 13 | "github.com/iotexproject/w3bstream/service/apinode/db" 14 | "github.com/iotexproject/w3bstream/task" 15 | ) 16 | 17 | type Clickhouse struct { 18 | db driver.Conn 19 | } 20 | 21 | func (p *Clickhouse) Retrieve(taskIDs []common.Hash) ([]*task.Task, error) { 22 | if len(taskIDs) == 0 { 23 | return nil, errors.New("empty query task ids") 24 | } 25 | taskIDsHex := make([]string, 0, len(taskIDs)) 26 | for _, t := range taskIDs { 27 | taskIDsHex = append(taskIDsHex, t.Hex()) 28 | } 29 | var ts []db.Task 30 | if err := p.db.Select(context.Background(), &ts, "SELECT * FROM w3bstream_tasks WHERE task_id IN ?", taskIDsHex); err != nil { 31 | return nil, errors.Wrap(err, "failed to query tasks") 32 | } 33 | 34 | // filter out prev task that has been fetched 35 | prevTasksPool := map[string]db.Task{} 36 | for i := range ts { 37 | prevTasksPool[ts[i].TaskID] = ts[i] 38 | } 39 | fetchPrevTaskIDs := make([]string, 0) 40 | for i := range ts { 41 | if ts[i].PrevTaskID != "" { 42 | if _, exist := prevTasksPool[ts[i].PrevTaskID]; !exist { 43 | fetchPrevTaskIDs = append(fetchPrevTaskIDs, ts[i].PrevTaskID) 44 | } 45 | } 46 | } 47 | 48 | if len(fetchPrevTaskIDs) != 0 { 49 | var pdts []db.Task 50 | if err := p.db.Select(context.Background(), &pdts, "SELECT * FROM w3bstream_tasks WHERE task_id IN ?", fetchPrevTaskIDs); err != nil { 51 | return nil, errors.Wrap(err, "failed to query previous tasks") 52 | } 53 | for i := range pdts { 54 | prevTasksPool[pdts[i].TaskID] = pdts[i] 55 | } 56 | } 57 | 58 | res := []*task.Task{} 59 | for i := range ts { 60 | t, err := p.conv(&ts[i]) 61 | if err != nil { 62 | return nil, err 63 | } 64 | if ts[i].PrevTaskID != "" { 65 | pdt, ok := prevTasksPool[ts[i].PrevTaskID] 66 | if !ok { 67 | return nil, errors.New("failed to get previous task") 68 | } 69 | pt, err := p.conv(&pdt) 70 | if err != nil { 71 | return nil, err 72 | } 73 | t.PrevTask = pt 74 | } 75 | res = append(res, t) 76 | } 77 | if len(res) != len(taskIDs) { 78 | return nil, errors.Errorf("cannot find all tasks, task_ids %v", taskIDs) 79 | } 80 | 81 | return res, nil 82 | } 83 | 84 | func (p *Clickhouse) conv(dt *db.Task) (*task.Task, error) { 85 | pid, ok := new(big.Int).SetString(dt.ProjectID, 10) 86 | if !ok { 87 | return nil, errors.New("failed to decode project id string") 88 | } 89 | sig, err := hexutil.Decode(dt.Signature) 90 | if err != nil { 91 | return nil, errors.Wrap(err, "failed to decode signature from hex format") 92 | } 93 | pubkey, err := hexutil.Decode(dt.DevicePubKey) 94 | if err != nil { 95 | return nil, errors.Wrap(err, "failed to decode device public key from hex format") 96 | } 97 | return &task.Task{ 98 | ID: common.HexToHash(dt.TaskID), 99 | Nonce: dt.Nonce, 100 | ProjectID: pid, 101 | ProjectVersion: dt.ProjectVersion, 102 | DevicePubKey: pubkey, 103 | Payload: []byte(dt.Payload), 104 | Signature: sig, 105 | PayloadHash: common.HexToHash(dt.PayloadHash), 106 | TaskHash: common.HexToHash(dt.TaskHash), 107 | }, nil 108 | } 109 | 110 | func NewClickhouse(dsn string) (*Clickhouse, error) { 111 | op, err := clickhouse.ParseDSN(dsn) 112 | if err != nil { 113 | return nil, errors.Wrap(err, "failed to parse clickhouse dsn") 114 | } 115 | conn, err := clickhouse.Open(op) 116 | if err != nil { 117 | return nil, errors.Wrap(err, "failed to connect clickhouse") 118 | } 119 | return &Clickhouse{db: conn}, nil 120 | } 121 | -------------------------------------------------------------------------------- /datasource/datasource.go: -------------------------------------------------------------------------------- 1 | package datasource 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/iotexproject/w3bstream/task" 6 | ) 7 | 8 | type Datasource interface { 9 | // If the provided taskID list is empty, an error will be reported 10 | // If the data for any task in the list cannot be retrieved, an error will be reported 11 | Retrieve(taskIDs []common.Hash) ([]*task.Task, error) 12 | } 13 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | ## Architecture 2 | 3 |

4 | 5 |

6 | 7 | ## Coordinator Architecture 8 | 9 |

10 | 11 |

12 | 13 | ## Prover Architecture 14 | 15 |

16 | 17 |

18 | -------------------------------------------------------------------------------- /docs/BUILD-CIRCUIT.md: -------------------------------------------------------------------------------- 1 | # Build circuit 2 | 3 | ## Get Repository 4 | ```bash 5 | git clone https://github.com/iotexproject/w3bstream.git 6 | cd w3bstream 7 | ``` 8 | 9 | ## Compile customized circuits 10 | 11 | ### Compile the customized Halo2 circuit 12 | 13 | 1. Install `wasm-pack` 14 | ```bash 15 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 16 | ``` 17 | 18 | 2. Build wasm 19 | 20 | ```bash 21 | cd examples/halo2-circuit/ 22 | wasm-pack build --target nodejs --out-dir pkg 23 | ``` 24 | 25 | you will find `halo2_wasm_bg.wasm` under the `pkg` folder. 26 | 27 | 3. (Optional) You can also write your circuit according to the [halo2 development documentation](https://zcash.github.io/halo2/user/simple-example.html), and put the circuit file in `src/circuits`; replace the `TODO` in `src/lib.rs` and build wasm with `wasm-pack build --target nodejs --out-dir pkg`. 28 | 29 | More details and options for `Halo2 circuit` are given in [its README](./examples/halo2-circuit/README.md). 30 | 31 | ### Compile the customized Risc0 circuits 32 | 33 | 1. Build 34 | 35 | ```bash 36 | cd examples/risc0-circuit/ 37 | cargo build --release 38 | ``` 39 | 40 | The path of `methods.rs` will be printed to the console, like this 41 | 42 | ```bash 43 | warning: methods_path is: "w3bstream/examples/risc0-circuits/target/release/build/risc0-circuits-5efc4ff59af940ab/out/methods.rs" 44 | ``` 45 | 46 | More details and options for `Risc0 circuit` are given in [its README](./examples/risc0-circuit/README.md). 47 | 48 | ### Compile the customized zkWasm circuits 49 | 50 | 1. Build 51 | 52 | ```bash 53 | cd examples/zkwasm-circuit/ 54 | asc src/add.ts -O --noAssert -o zkwasm_demo.wasm 55 | ``` 56 | 57 | More details and options for `zkWasm circuit` are given in [its README](./examples/zkwasm-circuit/README.md). 58 | 59 | 60 | ## Convert compiled circuit to w3bstream project config 61 | 62 | ### Convert halo2 circuit to w3bstream project config 63 | 64 | ```bash 65 | ioctl ws project config -t 2 -i "halo2_wasm_bg.wasm" 66 | # if you need set datasource, you should add -s 67 | ioctl ws project config -s "postgres://postgres:mysecretpassword@postgres:5432/w3bstream?sslmode=disable" -t 2 -i "halo2_wasm_bg.wasm" 68 | ``` 69 | 70 | This command will generate a file named `halo2-config.json` in the current folder. 71 | Or you can run `ioctl ws project config -t 2 -i "halo2_wasm_bg.wasm" -o "path/filename.json"` 72 | 73 | ### Convert risc0 circuit to w3bstream project config 74 | 75 | ```bash 76 | ioctl ws project config -t 1 -i "methods.rs" -e "{\"image_id\":\"RANGE_ID\", \"elf\":\"RANGE_ELF\"}" 77 | # if you need set datasource, you should add -s 78 | ioctl ws project config -s "postgres://postgres:mysecretpassword@postgres:5432/w3bstream?sslmode=disable" -t 1 -i "methods.rs" -e "{\"image_id\":\"RANGE_ID\", \"elf\":\"RANGE_ELF\"}" 79 | ``` 80 | The values of `image_id` and `elf` are variable names, and will be found in the `methods.rs`. 81 | 82 | This command will generate a file named `risc0-config.json` in the current folder. 83 | Or you can run `ioctl ws project config -t 1 -i "methods.rs" -o "path/filename.json" -e "{\"image_id\":\"RANGE_ID\", \"elf\":\"RANGE_ELF\"}` 84 | 85 | ### Convert zkwasm circuit to w3bstream project config 86 | 87 | ```bash 88 | ioctl ws project config -t 3 -i "zkwasm_demo.wasm" 89 | # if you need set datasource, you should add -s 90 | ioctl ws project config -s "postgres://postgres:mysecretpassword@postgres:5432/w3bstream?sslmode=disable" -t 3 -i "zkwasm_demo.wasm" 91 | ``` 92 | 93 | This command will generate a file named `zkwasm-config.json` in the current folder. 94 | Or you can run `ioctl ws project config -t 3 -i "zkwasm_demo.wasm" -o "path/filename.json"` 95 | -------------------------------------------------------------------------------- /docs/arch_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/docs/arch_new.png -------------------------------------------------------------------------------- /docs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/docs/architecture.png -------------------------------------------------------------------------------- /docs/coordinator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/docs/coordinator.png -------------------------------------------------------------------------------- /docs/geod.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | https://github.com/foundry-rs/foundry 4 | need the tools for interacting with smart contracts 5 | 6 | ### Testnet 7 | 8 | #### Endpoint 9 | https://dragonfruit-testnet.w3bstream.com/v1/task 10 | 11 | #### bind w3bstream project 12 | ```bash 13 | cast send 0xB564996622CE5610b9cF4ed35160f406185d7d0b "register(uint256)" 942 --private-key "your private key" --rpc-url "https://babel-api.testnet.iotex.io" --legacy 14 | ``` 15 | ```bash 16 | cast send 0x7D3158166E9298fC47beA036fE162fEA17632E5D "updateConfig(uint256,string,bytes32)" 942 ipfs://ipfs.mainnet.iotex.io/QmUHfDnvWrr2wiC78dw85xfctzawNWAN1TEbzosxwHdzYC 0x8153291c230dd107f102f75e826a11d9d4a8ac3f0f4e1c3619e547f82a94410e --private-key "your private key" --rpc-url "https://babel-api.testnet.iotex.io" --legacy 17 | ``` 18 | ```bash 19 | cast send 0x7D3158166E9298fC47beA036fE162fEA17632E5D "resume(uint256)" 942 --private-key "your private key" --rpc-url "https://babel-api.testnet.iotex.io" --legacy 20 | ``` 21 | 22 | #### bind dapp 23 | ```bash 24 | cast send 0x19dD7163Ad80fE550C97Affef49E1995B24941B1 "bindDapp(uint256,address)" 942 0xB2Dda5D9E65E44749409E209d8b7b15fb4e82147 --private-key "your private key" --rpc-url "https://babel-api.testnet.iotex.io" --legacy 25 | ``` 26 | 27 | 28 | ### Mainnet 29 | 30 | #### Endpoint 31 | https://dragonfruit-mainnet.w3bstream.com/v1/task 32 | 33 | #### bind w3bstream project 34 | ```bash 35 | cast send 0x97c3696E5f9A17569711B002152fd1603f8F06eB "register(uint256)" 9 --private-key "your private key" --rpc-url "https://babel-api.mainnet.iotex.io" --legacy 36 | ``` 37 | ```bash 38 | cast send 0xee8e318C712aB1731f9c3b708a5Caf2533614AF3 "updateConfig(uint256,string,bytes32)" 9 ipfs://ipfs.mainnet.iotex.io/QmPmnceezQsgWQRwR9seYLQ666rEkfxi4LgLCiLJeBqMpA 0xba270fc9a9a0817e1086ce2ecfd9c951b644a1aa628beb38b18e734c68a7e1f0 --private-key "your private key" --rpc-url "https://babel-api.mainnet.iotex.io" --legacy 39 | ``` 40 | ```bash 41 | cast send 0xee8e318C712aB1731f9c3b708a5Caf2533614AF3 "resume(uint256)" 9 --private-key "your private key" --rpc-url "https://babel-api.mainnet.iotex.io" --legacy 42 | ``` 43 | 44 | #### bind dapp 45 | ```bash 46 | cast send 0xeBf9Ab649f9952F9B6e85e59Fac9fED43594e3E0 "bindDapp(uint256,address)" 9 0xb6E9b57016288cBcd87B393E8a604F849bd77805 --private-key "your private key" --rpc-url "https://babel-api.mainnet.iotex.io" --legacy 47 | ``` -------------------------------------------------------------------------------- /docs/pebble.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | https://github.com/foundry-rs/foundry 4 | need the tools for interacting with smart contracts 5 | 6 | ### Mainnet 7 | 8 | #### Endpoint 9 | https://dragonfruit-mainnet.w3bstream.com/v1/task 10 | 11 | #### bind w3bstream project 12 | ```bash 13 | cast send 0x425D3FD5e8e0d0d7c73599adeb9B395505581ec7 "register(uint256)" 6 --private-key "your private key" --rpc-url "https://babel-api.mainnet.iotex.io" --legacy 14 | ``` 15 | ```bash 16 | cast send 0x6EF4559f2023C93F78d27E0151deF083638478d2 "updateConfig(uint256,string,bytes32)" 6 ipfs://ipfs.mainnet.iotex.io/QmQfaXAr7ZHbn1inDKigx9GcaHkZqxJiiHsA81GfCTRYZ6 0x9273b57144d4df4c6fb2a5850a1a95891c1b12f92d06909d875c3a4afe18bae4 --private-key "your private key" --rpc-url "https://babel-api.mainnet.iotex.io" --legacy 17 | ``` 18 | ```bash 19 | cast send 0x6EF4559f2023C93F78d27E0151deF083638478d2 "resume(uint256)" 6 --private-key "your private key" --rpc-url "https://babel-api.mainnet.iotex.io" --legacy 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/prover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/docs/prover.png -------------------------------------------------------------------------------- /docs/sequencer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/docs/sequencer.png -------------------------------------------------------------------------------- /docs/task_flow_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/docs/task_flow_diagram.png -------------------------------------------------------------------------------- /e2e/services/chain.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/core/types" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | "github.com/testcontainers/testcontainers-go" 13 | "github.com/testcontainers/testcontainers-go/wait" 14 | ) 15 | 16 | type chainContainer struct { 17 | testcontainers.Container 18 | } 19 | 20 | func SetupLocalChain() (*chainContainer, string, error) { 21 | ctx := context.Background() 22 | req := testcontainers.ContainerRequest{ 23 | Image: "ghcr.io/foundry-rs/foundry:latest", 24 | ExposedPorts: []string{"8545/tcp"}, 25 | // Entrypoint: []string{"anvil", "--block-time", "5", "--host", "0.0.0.0"}, 26 | Entrypoint: []string{"anvil", "--host", "0.0.0.0"}, 27 | WaitingFor: wait.ForListeningPort("8545"), 28 | } 29 | container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 30 | ContainerRequest: req, 31 | Started: true, 32 | Reuse: false, 33 | }) 34 | if err != nil { 35 | return nil, "", err 36 | } 37 | 38 | mapPort, err := container.MappedPort(ctx, "8545") 39 | if err != nil { 40 | return nil, "", err 41 | } 42 | 43 | ip, err := container.Host(ctx) 44 | if err != nil { 45 | return nil, "", err 46 | } 47 | 48 | endpoint := fmt.Sprintf("http://%s:%s", ip, mapPort.Port()) 49 | 50 | if err := TestChain(endpoint); err != nil { 51 | return nil, "", err 52 | } 53 | 54 | return &chainContainer{Container: container}, endpoint, nil 55 | } 56 | 57 | func TestChain(endpoint string) error { 58 | ethClient, err := ethclient.Dial(endpoint) 59 | if err != nil { 60 | return err 61 | } 62 | _, err = ethClient.NetworkID(context.Background()) 63 | if err != nil { 64 | return err 65 | 66 | } 67 | return nil 68 | } 69 | 70 | var ( 71 | txTimeout = 10 * time.Second 72 | ) 73 | 74 | // WaitForTransactionReceipt waits for the transaction receipt or returns an error if it times out 75 | func WaitForTransactionReceipt(client *ethclient.Client, txHash common.Hash) (*types.Receipt, error) { 76 | ctx, cancel := context.WithTimeout(context.Background(), txTimeout) 77 | defer cancel() 78 | 79 | ticker := time.NewTicker(1 * time.Second) 80 | defer ticker.Stop() 81 | 82 | for { 83 | select { 84 | case <-ctx.Done(): 85 | return nil, errors.New("transaction receipt timeout") 86 | case <-ticker.C: 87 | receipt, err := client.TransactionReceipt(context.Background(), txHash) 88 | if err == nil && receipt != nil { 89 | if receipt.Status != types.ReceiptStatusSuccessful { 90 | return nil, errors.New("transaction failed") 91 | } 92 | return receipt, nil 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /e2e/services/clickhouse.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/testcontainers/testcontainers-go" 7 | "github.com/testcontainers/testcontainers-go/modules/clickhouse" 8 | ) 9 | 10 | type chContainer struct { 11 | testcontainers.Container 12 | } 13 | 14 | func SetupClickhouse(dbName string) (*chContainer, string, error) { 15 | ctx := context.Background() 16 | dbUser := "default" 17 | dbPassword := "password" 18 | 19 | clickhouseContainer, err := clickhouse.Run(ctx, 20 | "clickhouse/clickhouse-server:24.8-alpine", 21 | clickhouse.WithDatabase(dbName), 22 | clickhouse.WithUsername(dbUser), 23 | clickhouse.WithPassword(dbPassword), 24 | ) 25 | if err != nil { 26 | return nil, "", err 27 | } 28 | 29 | dsn, err := clickhouseContainer.ConnectionString(ctx, "secure=false") 30 | if err != nil { 31 | return nil, "", err 32 | } 33 | 34 | return &chContainer{Container: clickhouseContainer}, dsn, nil 35 | } 36 | -------------------------------------------------------------------------------- /e2e/services/ipfs.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/testcontainers/testcontainers-go" 10 | "github.com/testcontainers/testcontainers-go/wait" 11 | 12 | "github.com/iotexproject/w3bstream/util/ipfs" 13 | ) 14 | 15 | type ipfsContainer struct { 16 | testcontainers.Container 17 | } 18 | 19 | func SetupIPFS() (*ipfsContainer, string, error) { 20 | ctx := context.Background() 21 | req := testcontainers.ContainerRequest{ 22 | Image: "ipfs/go-ipfs:latest", 23 | ExposedPorts: []string{"5001/tcp", "8080/tcp", "4001/tcp"}, 24 | WaitingFor: wait.ForHTTP("/version").WithPort("5001/tcp").WithStartupTimeout(2 * time.Minute), 25 | } 26 | container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 27 | ContainerRequest: req, 28 | Started: true, 29 | Reuse: false, 30 | }) 31 | if err != nil { 32 | return nil, "", err 33 | } 34 | 35 | mapPort, err := container.MappedPort(ctx, "5001") 36 | if err != nil { 37 | return nil, "", err 38 | } 39 | 40 | ip, err := container.Host(ctx) 41 | if err != nil { 42 | return nil, "", err 43 | } 44 | 45 | apiURL := fmt.Sprintf("%s:%s", ip, mapPort.Port()) 46 | 47 | return &ipfsContainer{Container: container}, apiURL, nil 48 | } 49 | 50 | func TestIPfs2(apiURL string) { 51 | ipfs := ipfs.NewIPFS(apiURL) 52 | 53 | // Add file to IPFS 54 | cid, err := ipfs.AddContent([]byte("Hello, IPFS!")) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | fmt.Println("File added with CID:", cid) 59 | 60 | // Get file from IPFS 61 | content, err := ipfs.Cat(cid) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | fmt.Println("File content:", string(content)) 66 | } 67 | -------------------------------------------------------------------------------- /e2e/services/vm.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/testcontainers/testcontainers-go" 8 | "github.com/testcontainers/testcontainers-go/wait" 9 | ) 10 | 11 | type VMContainer struct { 12 | testcontainers.Container 13 | } 14 | 15 | func SetupGnarkVM() (*VMContainer, string, error) { 16 | ctx := context.Background() 17 | req := testcontainers.ContainerRequest{ 18 | Image: "ghcr.io/iotexproject/gnarkserver:v0.0.20", 19 | ExposedPorts: []string{"4005/tcp"}, 20 | WaitingFor: wait.ForListeningPort("4005"), 21 | } 22 | container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 23 | ContainerRequest: req, 24 | Started: true, 25 | Reuse: false, 26 | }) 27 | if err != nil { 28 | return nil, "", err 29 | } 30 | 31 | mapPort, err := container.MappedPort(ctx, "4005") 32 | if err != nil { 33 | return nil, "", err 34 | } 35 | 36 | ip, err := container.Host(ctx) 37 | if err != nil { 38 | return nil, "", err 39 | } 40 | 41 | endpoint := fmt.Sprintf("%s:%s", ip, mapPort.Port()) 42 | 43 | return &VMContainer{Container: container}, endpoint, nil 44 | } 45 | -------------------------------------------------------------------------------- /e2e/testdata/gnark.code: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/e2e/testdata/gnark.code -------------------------------------------------------------------------------- /e2e/testdata/gnark.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/e2e/testdata/gnark.metadata -------------------------------------------------------------------------------- /e2e/testdata/liveness.circuit: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:13e655a7446c2304ad82b37c06eba7832b6f18989fff9fb2b6b66459efb52d97 3 | size 20795840 4 | -------------------------------------------------------------------------------- /e2e/testdata/liveness.pk: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:af759d932be298d4352d8ecd3dab23d82198068ff20e8158b0bfe4a40e7d9137 3 | size 79888897 4 | -------------------------------------------------------------------------------- /e2e/testdata/movement.circuit: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ae33b4b5ce392ba4e0f6c2590e6b6180302df6c881acb22231872dce3faaad9d 3 | size 29671791 4 | -------------------------------------------------------------------------------- /e2e/testdata/movement.pk: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4b96d85d2ff1d869561bede009e4af361f52d28d560021679f87af0a4468979f 3 | size 122624679 4 | -------------------------------------------------------------------------------- /examples/halo2-circuit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "halo2-simple-circuit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_04_20", features = ["dev-graph"]} 8 | halo2_curves = { git = "https://github.com/privacy-scaling-explorations/halo2curves", tag = "0.3.2", package = "halo2curves" } 9 | snark_verifier = { git = "https://github.com/privacy-scaling-explorations/snark-verifier.git", rev="fedd7a8", package = "snark-verifier" } 10 | rand = "0.8.5" 11 | itertools = "0.11.0" 12 | hex = "0.4.3" 13 | plotters = { version = "0.3.0", default-features = true } 14 | wasm-bindgen = "0.2.87" 15 | getrandom = { version = "0.2", features = ["js"] } 16 | serde = { version = "1.0", features = ["derive"] } 17 | serde_json = "1.0" 18 | serde_derive = "1.0" 19 | clap = { version = "4.4.11", features = ["derive"] } 20 | ethabi = "18.0.0" 21 | 22 | [lib] 23 | crate-type = ["cdylib", "rlib"] 24 | 25 | [package.metadata.wasm-pack.profile.release] 26 | wasm-opt = false 27 | -------------------------------------------------------------------------------- /examples/halo2-circuit/README.md: -------------------------------------------------------------------------------- 1 | Halo2 wasm template 2 | ================== 3 | 4 | ## Preparation 5 | install `wasm-pack` 6 | ``` shell 7 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 8 | ``` 9 | 10 | install `solc` 11 | Reference [install guid](https://docs.soliditylang.org/en/v0.8.9/installing-solidity.html) 12 | 13 | ## Build halo2 wasm program 14 | 1. get template 15 | 16 | ``` shell 17 | git clone git@github.com:iotexproject/w3bstream.git && cd examples/halo2-circuit 18 | ``` 19 | 20 | 2. build wasm 21 | 22 | ``` shell 23 | wasm-pack build --target nodejs --out-dir pkg 24 | ``` 25 | 26 | you will find `halo2_simple_bg.wasm` in the `pkg` folder. 27 | 28 | ## Advanced 29 | You can also develop your own halo2 circuit program. 30 | 31 | 1. Write a circuit according to the [halo2 development documentation](https://zcash.github.io/halo2/user/simple-example.html), and put the circuit file in `src/circuits`. 32 | 2. Replace the `TODO` in `src/lib.rs`. 33 | 3. Build wasm with `wasm-pack build --target nodejs --out-dir pkg`. 34 | 35 | ## Build executable file 36 | 37 | ``` shell 38 | cargo build --release 39 | ``` 40 | 41 | After this command is successful, a `halo2-simple-circuit` executable file(executable file corresponding to the [simple circuit](./src/circuits/simple.rs)) will be generated in the `target/release` directory. 42 | 43 | > **_NOTE:_** 44 | > If you want to build an executable file corresponding to your own circuit, you need to replace the `TODO` in `src/main.rs`. 45 | 46 | ## Generate verify smart contract 47 | 48 | ``` shell 49 | target/release/halo2-simple-circuit solidity 50 | ``` 51 | You will find `Verifier.sol` under the current folder. Or you can run `target/release/halo2-simple-circuit solidity -f path/filename.sol`. 52 | Then you can deploy the smart contract to IoTeX chain or other ETH-compatible chains. 53 | 54 | ## Local verify proof 55 | 1. Get halo2 proof 56 | if you can send messages to prover successfully, then you can execute `ioctl ws message send --project-id 10001 --project-version "0.1" --data "{\"private_a\": 3, \"private_b\": 4}"` to obtain a halo2 proof, then put it in a file, like `halo2-simple-proof.json`. 57 | 58 | 2. verify 59 | `--proof` is proof file, 60 | `--public` is the public input 61 | `--project` is the project id 62 | `--task` is the task id 63 | 64 | ``` shell 65 | target/release/halo2-simple-circuit verify --proof halo2-simple-proof.json --public 567 --project 92 --task 35 66 | ``` 67 | -------------------------------------------------------------------------------- /examples/halo2-circuit/contracts/Halo2Dapp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | contract Halo2Dapp { 6 | 7 | uint256 constant bn254Prime = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 8 | // halo2 verification contract 9 | address private halo2Verifier; 10 | 11 | uint256 public projectId; 12 | uint256 public proverId; 13 | string public clientId; 14 | bytes public data; 15 | 16 | function setReceiver(address _receiver) public { 17 | halo2Verifier = _receiver; 18 | } 19 | 20 | function getReceiver() public view returns (address ){ 21 | return halo2Verifier; 22 | } 23 | 24 | // verifier is the verify contract, it was generated by circuit. 25 | function process(uint256 _projectId, uint256 _proverId, string memory _clientId, bytes calldata _data) public { 26 | require(halo2Verifier != address(0), "verifier address not set"); 27 | projectId = _projectId; 28 | proverId = _proverId; 29 | clientId = _clientId; 30 | data = _data; 31 | 32 | (uint256 publicInput, uint256 taskID, bytes memory _proof) = abi.decode(_data, (uint256, uint256, bytes)); 33 | bytes32 _publicInput = uint256ToFr(publicInput); 34 | bytes32 _taskID = uint256ToFr(taskID); 35 | bytes32 _projectID = uint256ToFr(projectId); 36 | 37 | bytes memory callData = abi.encodePacked(_publicInput, _projectID, _taskID, _proof); 38 | 39 | (bool success,) = halo2Verifier.staticcall(callData); 40 | require(success, "Failed to verify proof"); 41 | // TODO 42 | } 43 | 44 | function uint256ToFr(uint256 _value) public pure returns (bytes32) { 45 | return bytes32(_value % bn254Prime); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/halo2-circuit/src/circuits/empty.rs: -------------------------------------------------------------------------------- 1 | use halo2_curves::ff::Field; 2 | use halo2_proofs::{ 3 | circuit::{Layouter, SimpleFloorPlanner, Value}, 4 | plonk::{Advice, Circuit, Column, ConstraintSystem, Instance, Selector}, 5 | }; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct Config { 9 | pub advice: Column, 10 | pub instance: Column, 11 | pub selector: Selector, 12 | } 13 | 14 | #[derive(Default, Clone)] 15 | pub struct EmptyCircuit { 16 | pub constant: F, 17 | pub a: Value, 18 | } 19 | 20 | impl Circuit for EmptyCircuit { 21 | type Config = Config; 22 | type FloorPlanner = SimpleFloorPlanner; 23 | 24 | fn without_witnesses(&self) -> Self { 25 | Self::default() 26 | } 27 | 28 | fn configure(_meta: &mut ConstraintSystem) -> Self::Config { 29 | todo!() 30 | } 31 | 32 | fn synthesize( 33 | &self, 34 | _config: Self::Config, 35 | mut _layouter: impl Layouter, 36 | ) -> Result<(), halo2_proofs::plonk::Error> { 37 | todo!() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/halo2-circuit/src/circuits/gadgets/mod.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::halo2curves::{ 2 | bn256::{Fq, Fr}, 3 | ff::{Field as Halo2Field, FromUniformBytes, PrimeField}, 4 | }; 5 | 6 | pub mod is_zero; 7 | 8 | pub trait Field: Halo2Field + PrimeField + FromUniformBytes<64> + Ord {} 9 | impl Field for Fr {} 10 | impl Field for Fq {} 11 | -------------------------------------------------------------------------------- /examples/halo2-circuit/src/circuits/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod empty; 2 | pub mod fibonacci; 3 | pub mod function; 4 | pub mod gadgets; 5 | pub mod simple; 6 | -------------------------------------------------------------------------------- /examples/halo2-circuit/src/opts.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Debug, Parser)] 4 | #[clap(name = "halo2-circuit", version = "0.1.0")] 5 | pub struct Opts { 6 | #[clap(subcommand)] 7 | pub sub: Subcommands, 8 | } 9 | 10 | #[derive(Debug, Subcommand)] 11 | pub enum Subcommands { 12 | #[clap(name = "solidity")] 13 | #[clap(about = "Generate verifier solidity contract.")] 14 | Solidity { 15 | #[clap( 16 | long, 17 | short, 18 | value_name = "file", 19 | default_value = "Verifier.sol" 20 | )] 21 | file: String, 22 | }, 23 | 24 | #[clap(name = "proof")] 25 | #[clap(about = "Generate proof.")] 26 | Proof { 27 | #[clap( 28 | long, 29 | value_name = "private_a", 30 | default_value = "3" 31 | )] 32 | private_a: u64, 33 | #[clap( 34 | long, 35 | value_name = "private_b", 36 | default_value = "4" 37 | )] 38 | private_b: u64, 39 | #[clap( 40 | long, 41 | value_name = "project_id", 42 | )] 43 | project_id: u64, 44 | #[clap( 45 | long, 46 | value_name = "task_id", 47 | )] 48 | task_id: u64, 49 | }, 50 | 51 | #[clap(name = "verify")] 52 | #[clap(about = "Local verify proof.")] 53 | Verfiy { 54 | #[clap(long, value_name = "proof-file")] 55 | proof: String, 56 | #[clap(long, value_name = "public-input")] 57 | public: u64, 58 | #[clap(long, value_name = "project-input")] 59 | project: u64, 60 | #[clap(long, value_name = "task-input")] 61 | task: u64, 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /examples/risc0-circuit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "risc0-circuit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | range-method = { path = "method" } 8 | risc0-zkvm = { version = "1.0.0" } 9 | serde = "1.0" 10 | serde_json = "1.0" 11 | clap = { version = "4.4.3", features = [ 12 | "derive", 13 | "env", 14 | "unicode", 15 | "wrap_help", 16 | ] } 17 | 18 | 19 | [features] 20 | cuda = ["risc0-zkvm/cuda"] 21 | default = [] 22 | metal = ["risc0-zkvm/metal"] 23 | -------------------------------------------------------------------------------- /examples/risc0-circuit/README.md: -------------------------------------------------------------------------------- 1 | Risc0 local verify proof 2 | ================== 3 | 4 | ## Verify with docker image 5 | 1. Get stark proof and image id 6 | If you have successfully built `methods.rs` in the `method` path, you can find `xx_ID`(this is `RANGE_ID` in example `methods.rs`), the corresponding [u32; 8] array for `xx_ID` without `[]` is `image-id` string. 7 | If you have successfully deployed `methods.rs` to w3bstream, and you can send messages to prover successfully, then you can execute `ioctl ws message send --project-id 10000 --project-version "0.1" --data "{\"private_input\":\"14\", \"public_input\":\"3,34\", \"receipt_type\":\"Stark\"}"` to obtain a stark proof, then put it in a file, like `start-proof.json`. 8 | 9 | 2. Execute the `docker run` command for local verification. Note that the directory where the proof is located needs to be mounted into the image. 10 | It's simple, just input the proof file and image-id. You can also use the help command to check how to use it. 11 | 12 | ```shell 13 | docker run -v /host/stark-proof.json:/stark-proof.json iotexdev/zkverifier:v0.0.1 /verifier/risc0-circuit verify -p /stark-proof.json -i "520991199, 1491489009, 3725421922, 2701107036, 261900524, 710029518, 655219346, 3077599842" 14 | ``` 15 | 16 | ## Verify with binary file 17 | ### Build Verifier 18 | 1. Install the `risc0` toolchain 19 | 20 | ``` shell 21 | cargo install cargo-binstall 22 | cargo binstall cargo-risczero 23 | cargo risczero install 24 | ``` 25 | 26 | 2. build release 27 | 28 | ``` shell 29 | cargo build --release 30 | ``` 31 | 32 | After this command is successful, a `risc0-circuit` executable file will be generated in the `target/release` directory. 33 | 34 | ### Verify 35 | You can execute the binary file in the `target/release` directory. It's simple, just input the proof file and image-id. You can also use the help command to check how to use it. 36 | 37 | ``` shell 38 | target/release/risc0-circuit verify -p stark-proof.json -i "520991199, 1491489009, 3725421922, 2701107036, 261900524, 710029518, 655219346, 3077599842" 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /examples/risc0-circuit/contract/ControlID.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2024 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // This file is automatically generated by: 18 | // cargo xtask bootstrap-groth16 19 | 20 | pragma solidity ^0.8.9; 21 | 22 | library ControlID { 23 | bytes32 public constant CONTROL_ROOT = hex"a516a057c9fbf5629106300934d48e0e775d4230e41e503347cad96fcbde7e2e"; 24 | // NOTE: This has opposite byte order to the value in the risc0 repository. 25 | bytes32 public constant BN254_CONTROL_ID = hex"0eb6febcf06c5df079111be116f79bd8c7e85dc9448776ef9a59aaf2624ab551"; 26 | } -------------------------------------------------------------------------------- /examples/risc0-circuit/contract/Risc0Dapp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | interface IRisc0Receiver { 6 | function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external view; 7 | } 8 | 9 | contract Risc0Dapp { 10 | 11 | bytes public proof; 12 | bytes public proof_seal; 13 | bytes32 public proof_journal; 14 | uint256 public projectId; 15 | uint256 public proverId; 16 | string public clientId; 17 | // risc0 verification contract 18 | address private risc0Verifier; 19 | 20 | mapping(uint256 => bytes32) private projectIdToImageId; 21 | 22 | 23 | function process(uint256 _projectId, uint256 _proverId, string memory _clientId, bytes calldata _data) public { 24 | projectId = _projectId; 25 | proverId = _proverId; 26 | clientId = _clientId; 27 | proof = _data; 28 | (bytes memory proof_snark_seal, bytes memory proof_snark_journal) = abi.decode(_data, (bytes, bytes)); 29 | proof_seal = proof_snark_seal; 30 | proof_journal = sha256(proof_snark_journal); 31 | // verify zk proof 32 | IRisc0Receiver(risc0Verifier).verify(proof_seal, projectIdToImageId[projectId], proof_journal); 33 | // TODO 34 | } 35 | 36 | function setProjectIdToImageId(uint256 _projectId, bytes32 _imageId) public { 37 | projectIdToImageId[_projectId] = _imageId; 38 | } 39 | 40 | function getImageIdByProjectId(uint256 _projectId) public view returns (bytes32) { 41 | return projectIdToImageId[_projectId]; 42 | } 43 | 44 | function setReceiver(address _receiver) public { 45 | risc0Verifier = _receiver; 46 | } 47 | 48 | function getReceiver() public view returns (address ){ 49 | return risc0Verifier; 50 | } 51 | } -------------------------------------------------------------------------------- /examples/risc0-circuit/contract/StructHash.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2024 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | pragma solidity ^0.8.9; 18 | 19 | import {SafeCast} from "./SafeCast.sol"; 20 | 21 | import {reverseByteOrderUint16} from "./Util.sol"; 22 | 23 | /// @notice Structural hashing routines used for RISC Zero data structures. 24 | /// @dev 25 | /// StructHash implements hashing for structs, incorporating type tags for domain separation. 26 | /// The goals of this library are: 27 | /// * Collision resistance: it should not be possible to find two semantically distinct values that 28 | /// produce the same digest. 29 | /// * Simplicity: implementations should be simple to understand and write, as these methods must 30 | /// be implemented in multiple languages and environments, including zkSNARK circuits. 31 | /// * Incremental openings: it should be possible to incrementally open a nested struct without 32 | /// needing to open (very many) extra fields (i.e. the struct should be "Merkle-ized"). 33 | library StructHash { 34 | using SafeCast for uint256; 35 | 36 | // @notice Compute the struct digest with the given tag digest and digest fields down. 37 | function taggedStruct(bytes32 tagDigest, bytes32[] memory down) internal pure returns (bytes32) { 38 | bytes memory data = new bytes(0); 39 | return taggedStruct(tagDigest, down, data); 40 | } 41 | 42 | // @notice Compute the struct digest with the given tag digest, digest fields down, and data. 43 | function taggedStruct(bytes32 tagDigest, bytes32[] memory down, bytes memory data) 44 | internal 45 | pure 46 | returns (bytes32) 47 | { 48 | uint16 downLen = down.length.toUint16(); 49 | // swap the byte order to encode as little-endian. 50 | bytes2 downLenLE = bytes2((downLen << 8) | (downLen >> 8)); 51 | return sha256(abi.encodePacked(tagDigest, down, data, downLenLE)); 52 | } 53 | 54 | // @notice Add an element (head) to the incremental hash of a list (tail). 55 | function taggedListCons(bytes32 tagDigest, bytes32 head, bytes32 tail) internal pure returns (bytes32) { 56 | bytes32[] memory down = new bytes32[](2); 57 | down[0] = head; 58 | down[1] = tail; 59 | return taggedStruct(tagDigest, down); 60 | } 61 | 62 | // @notice Hash the list by using taggedListCons to repeatedly add to the head of the list. 63 | function taggedList(bytes32 tagDigest, bytes32[] memory list) internal pure returns (bytes32) { 64 | bytes32 curr = bytes32(0x0000000000000000000000000000000000000000000000000000000000000000); 65 | for (uint256 i = 0; i < list.length; i++) { 66 | curr = taggedListCons(tagDigest, list[list.length - 1 - i], curr); 67 | } 68 | return curr; 69 | } 70 | } -------------------------------------------------------------------------------- /examples/risc0-circuit/contract/Util.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2024 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | pragma solidity ^0.8.9; 18 | 19 | /// @notice reverse the byte order of the uint256 value. 20 | /// @dev Solidity uses a big-endian ABI encoding. Reversing the byte order before encoding 21 | /// ensure that the encoded value will be little-endian. 22 | /// Written by k06a. https://ethereum.stackexchange.com/a/83627 23 | function reverseByteOrderUint256(uint256 input) pure returns (uint256 v) { 24 | v = input; 25 | 26 | // swap bytes 27 | v = ((v & 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >> 8) 28 | | ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8); 29 | 30 | // swap 2-byte long pairs 31 | v = ((v & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >> 16) 32 | | ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16); 33 | 34 | // swap 4-byte long pairs 35 | v = ((v & 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >> 32) 36 | | ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32); 37 | 38 | // swap 8-byte long pairs 39 | v = ((v & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >> 64) 40 | | ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64); 41 | 42 | // swap 16-byte long pairs 43 | v = (v >> 128) | (v << 128); 44 | } 45 | 46 | /// @notice reverse the byte order of the uint32 value. 47 | /// @dev Solidity uses a big-endian ABI encoding. Reversing the byte order before encoding 48 | /// ensure that the encoded value will be little-endian. 49 | /// Written by k06a. https://ethereum.stackexchange.com/a/83627 50 | function reverseByteOrderUint32(uint32 input) pure returns (uint32 v) { 51 | v = input; 52 | 53 | // swap bytes 54 | v = ((v & 0xFF00FF00) >> 8) | ((v & 0x00FF00FF) << 8); 55 | 56 | // swap 2-byte long pairs 57 | v = (v >> 16) | (v << 16); 58 | } 59 | 60 | /// @notice reverse the byte order of the uint16 value. 61 | /// @dev Solidity uses a big-endian ABI encoding. Reversing the byte order before encoding 62 | /// ensure that the encoded value will be little-endian. 63 | /// Written by k06a. https://ethereum.stackexchange.com/a/83627 64 | function reverseByteOrderUint16(uint16 input) pure returns (uint16 v) { 65 | v = input; 66 | 67 | // swap bytes 68 | v = (v >> 8) | ((v & 0x00FF) << 8); 69 | } -------------------------------------------------------------------------------- /examples/risc0-circuit/method/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "range-method" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [build-dependencies] 7 | risc0-build = { version = "1.0.0" } 8 | 9 | [package.metadata.risc0] 10 | methods = ["guest"] 11 | -------------------------------------------------------------------------------- /examples/risc0-circuit/method/README.md: -------------------------------------------------------------------------------- 1 | Risc0 guest template 2 | ================== 3 | 4 | ## Build risc0 circuits program 5 | 1. get template 6 | 7 | ``` shell 8 | git clone git@github.com:iotexproject/w3bstream.git && cd examples/risc0-circuit/method 9 | ``` 10 | 11 | 2. build 12 | 13 | ``` shell 14 | cargo build --release 15 | ``` 16 | 17 | The directory of `methods.rs` will be printed to the console, like this 18 | ```shell 19 | warning: methods_path is: "w3bstream/examples/risc0-circuit/method/target/release/build/risc0-circuits-5efc4ff59af940ab/out/methods.rs" 20 | ``` 21 | 22 | ## Advanced 23 | You can also develop your own risc0 guest program. 24 | 25 | 1. Edit `guest/Cargo.toml`, changing the line `name = "method_name"` to instead read `name = "your_method_name"`. 26 | 2. Edit `guest/src/main.rs`, changing the `main` func. 27 | 3. Build wasm with `cargo build --release`, and the directory of `methods.rs` will be printed to the console. 28 | 29 | [more risc0 guest examples](https://github.com/risc0/risc0/tree/main/examples) 30 | -------------------------------------------------------------------------------- /examples/risc0-circuit/method/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::env; 16 | use std::path::Path; 17 | 18 | fn main() { 19 | risc0_build::embed_methods(); 20 | let out_dir = env::var("OUT_DIR").unwrap(); 21 | let methods_path = Path::new(&out_dir).join("methods.rs"); 22 | println!("cargo:warning=methods_path is: {:?}", methods_path); 23 | } 24 | -------------------------------------------------------------------------------- /examples/risc0-circuit/method/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "range" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | 8 | [dependencies] 9 | risc0-zkvm = { version = "1.1.2", default-features = false, features = ["std"] } 10 | serde_json = "1.0.132" 11 | -------------------------------------------------------------------------------- /examples/risc0-circuit/method/guest/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![no_main] 16 | // #![no_std] 17 | 18 | use risc0_zkvm::guest::env; 19 | use serde_json::Value as JsonValue; 20 | 21 | risc0_zkvm::guest::entry!(main); 22 | 23 | pub fn main() { 24 | let data: Vec = env::read(); 25 | env::log(&format!("data {:?}", data)); 26 | 27 | let v: JsonValue = serde_json::from_str(&data[0]).unwrap(); 28 | 29 | // Parse private input directly as u64 30 | let pri_a = v["private_input"].as_str().unwrap().parse::().unwrap(); 31 | 32 | // Split public input string and parse both numbers 33 | let public_input = v["public_input"].as_str().unwrap(); 34 | let pub_nums: Vec = public_input 35 | .split(',') 36 | .map(|s| s.trim().parse::().unwrap()) 37 | .collect(); 38 | 39 | let pub_b = pub_nums[0]; 40 | let pub_c = pub_nums[1]; 41 | 42 | if pri_a > pub_b && pri_a < pub_c { 43 | let s = format!( 44 | "I know your private input is greater than {} and less than {}, and I can prove it!", 45 | pub_b, pub_c 46 | ); 47 | env::commit(&s); 48 | } else { 49 | let s = format!( 50 | "I know your private input is not greater than {} or less than {}, and I can not prove it!", 51 | pub_b, pub_c 52 | ); 53 | env::commit(&s); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/risc0-circuit/method/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 RISC Zero, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | include!(concat!(env!("OUT_DIR"), "/methods.rs")); -------------------------------------------------------------------------------- /examples/risc0-circuit/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod opts; -------------------------------------------------------------------------------- /examples/risc0-circuit/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use clap::Parser; 4 | use risc0_circuit::opts::Opts; 5 | use risc0_zkvm::{serde::from_slice, Receipt}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Debug, Deserialize, Serialize)] 9 | pub enum RiscReceipt { 10 | /// The [Receipt]. 11 | Stark(Receipt), 12 | } 13 | 14 | fn main() { 15 | let opts = Opts::parse(); 16 | match opts.sub { 17 | risc0_circuit::opts::Subcommands::Verfiy { proof, image_id } => { 18 | let mut id: [u32; 8] = [0; 8]; 19 | let vec_u32: Result, _> = image_id 20 | .split(",") 21 | .into_iter() 22 | .map(|s| s.trim().parse::()) 23 | .collect(); 24 | 25 | match vec_u32 { 26 | Ok(v) => { 27 | if v.len() == id.len() { 28 | id.copy_from_slice(&v); 29 | } else { 30 | println!("The length of image_id is not 8."); 31 | return; 32 | } 33 | } 34 | Err(e) => println!("image_id parse error: {}", e), 35 | } 36 | 37 | let proof_raw = fs::read(proof).expect("read proof file error"); 38 | let proof_raw = String::from_utf8(proof_raw).unwrap(); 39 | let risc_receipt: RiscReceipt = serde_json::from_str(&proof_raw).unwrap(); 40 | match risc_receipt { 41 | RiscReceipt::Stark(receipt) => { 42 | let verify_result = receipt.verify(id); 43 | let result = match verify_result { 44 | Ok(_) => from_slice(&receipt.journal.bytes).expect( 45 | "Journal output should deserialize into the same types (& order) that it was written", 46 | ), 47 | Err(e) => format!("{}", e), 48 | }; 49 | println!("{:?}", result); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/risc0-circuit/src/opts.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Debug, Parser)] 4 | #[clap(name = "risc0-circuit", version = "0.1.0")] 5 | pub struct Opts { 6 | #[clap(subcommand)] 7 | pub sub: Subcommands, 8 | } 9 | 10 | #[derive(Debug, Subcommand)] 11 | 12 | pub enum Subcommands { 13 | #[clap(name = "verify")] 14 | #[clap(about = "Local verify proof.")] 15 | Verfiy { 16 | #[clap(long, short, value_name = "proof-file")] 17 | proof: String, 18 | #[clap(long, short, value_name = "image-id")] 19 | image_id: String, 20 | }, 21 | } -------------------------------------------------------------------------------- /examples/zkwasm-circuit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkwasm-circuit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | halo2_proofs = { git = "https://github.com/junyu0312/halo2.git", branch = "gpu", default-features = true } 8 | halo2aggregator-s = { git = "https://github.com/DelphinusLab/halo2aggregator-s.git", rev = "fa32d533b617f332728dcaee0f9ecbf54e972ec4" } 9 | delphinus-zkwasm = { git = "https://github.com/machinefi/zkwasm.git"} 10 | anyhow = "1.0.75" 11 | data-encoding = "2.4.0" 12 | serde = { version = "1.0.188", features = ["derive"] } 13 | serde_json = "1.0.107" 14 | clap = { version = "4.4.3", features = ["derive", "env", "unicode", "wrap_help"] } 15 | 16 | -------------------------------------------------------------------------------- /examples/zkwasm-circuit/README.md: -------------------------------------------------------------------------------- 1 | Zkwasm locally verify proof 2 | ================== 3 | 4 | ## Verify with docker image 5 | 1. Get zkwasm proof 6 | If you can send messages to prover successfully, then you can execute `ioctl ws message send --project-id 10002 --project-version "0.1" --data "{\"private_input\": [1, 1] , \"public_input\": [2] }"` to obtain a zkwasm proof, then put it in a file, like `zkwasm-proof.json`. 7 | 8 | 2. Execute the `docker run` command for local verification. Note that the directory where the proof is located needs to be mounted into the image. 9 | It's simple, just input the proof file. You can also use the help command to check how to use it. 10 | 11 | ```shell 12 | docker run -v /host/zkwasm-proof.json:/zkwasm-proof.json iotexdev/zkverifier /verifier/zkwasm-circuit verify -p /zkwasm-proof.json 13 | ``` 14 | 15 | ## Verify with binary file 16 | > **_NOTE:_** 17 | > Since a crate that `zkwasm-circuit` depends on is currently under development and is not suitable to be made public, 18 | > it is recommended to use a Docker image for local verification. 19 | 20 | ### Build Verifier 21 | 22 | ``` shell 23 | cargo build --release 24 | ``` 25 | 26 | After this command is successful, a `zkwasm-circuit` executable file will be generated in the `target/release` directory. 27 | 28 | ### Verify 29 | You can execute the binary file in the `target/release` directory. It's simple, just input the proof file. You can also use the help command to check how to use it. 30 | 31 | ``` shell 32 | target/release/zkwasm-circuit verify -p zkwasm-proof.json 33 | ``` 34 | 35 | > **_NOTE:_** 36 | > zkwasm just support single prove 37 | -------------------------------------------------------------------------------- /examples/zkwasm-circuit/circuit/README.md: -------------------------------------------------------------------------------- 1 | zkwasm template 2 | ================== 3 | 4 | ## Preparation 5 | Install AssemblyScript compiler 6 | 7 | ``` shell 8 | npm install -g assemblyscript 9 | ``` 10 | 11 | ## Build zkwasm program 12 | 1. get template 13 | 14 | ``` shell 15 | git clone git@github.com:iotexproject/w3bstream.git && cd examples/zkwasm-circuit/circuit 16 | ``` 17 | 18 | 2. build 19 | 20 | ``` shell 21 | asc src/add.ts -O --noAssert -o add.wasm 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/zkwasm-circuit/circuit/src/add.ts: -------------------------------------------------------------------------------- 1 | @external("env", "wasm_input") 2 | declare function wasm_input(x: i32): i64 3 | 4 | @external("env", "require") 5 | declare function require(x: i32): void 6 | 7 | export function read_public_input(): i64 { 8 | return wasm_input(1); 9 | } 10 | 11 | export function read_private_input(): i64 { 12 | return wasm_input(0); 13 | } 14 | 15 | export function zkmain(): void { 16 | let a = read_private_input(); 17 | let b = read_private_input(); 18 | let expect = read_public_input(); 19 | 20 | require(a+b == expect); 21 | } 22 | -------------------------------------------------------------------------------- /examples/zkwasm-circuit/setup/K18.params: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotexproject/w3bstream/5469532e33b47b2d12cfaf577d1c214169447b69/examples/zkwasm-circuit/setup/K18.params -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::{Seek, Read}}; 2 | 3 | use anyhow::Result; 4 | use delphinus_zkwasm::circuits::TestCircuit; 5 | use halo2_proofs::{poly::commitment::Params, arithmetic::Engine, pairing::bn256::Bn256}; 6 | 7 | use crate::{types::{proof::ProofData, vkey::VkeyMetadata, BinarySerializer}, prover::verifier}; 8 | 9 | pub mod types; 10 | mod prover; 11 | pub mod opts; 12 | 13 | pub fn verify( 14 | params: &Params<::G1Affine>, 15 | proof_tmp_file: &mut File, 16 | ) -> Result<()> { 17 | 18 | // 1. load proof and instance 19 | let pd = ProofData::from_binary(&load_binary_from_file(proof_tmp_file)).unwrap(); 20 | let (proof, instances, vk) = (pd.proof, pd.instances, pd.vk); 21 | 22 | // 2. load vkey 23 | let vk = VkeyMetadata::from_binary(&vk).unwrap(); 24 | let vkey = vk.load_vkey_with::>(¶ms).unwrap(); 25 | 26 | // 3. build verifier 27 | let mut verifier = verifier::ZKVerifier::::new(); 28 | verifier.load_params(¶ms); 29 | verifier.load_vkey(&vkey); 30 | return verifier.verify_proof(&instances.0, &proof); 31 | } 32 | 33 | pub fn load_binary_from_file(fd: &mut File) -> Vec { 34 | fd.seek(std::io::SeekFrom::Start(0)).unwrap(); 35 | let mut ret = vec![]; 36 | fd.read_to_end(&mut ret).unwrap(); 37 | ret 38 | } -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use halo2_proofs::{poly::commitment::Params, pairing::bn256::Bn256, arithmetic::Engine}; 3 | use zkwasm_circuit::{types::BinarySerializer, load_binary_from_file, verify, opts::Opts}; 4 | 5 | 6 | const PARAMS_FILE_PATH: &str = "./setup/K18.params"; 7 | 8 | fn main() { 9 | let opts = Opts::parse(); 10 | match opts.sub { 11 | zkwasm_circuit::opts::Subcommands::Verfiy { proof } => { 12 | let mut params_file = std::fs::File::open(PARAMS_FILE_PATH).unwrap(); 13 | let params = Params::<::G1Affine>::from_binary(&load_binary_from_file(&mut params_file)).unwrap(); 14 | 15 | let mut proof_tmp_file = std::fs::File::open(proof).unwrap(); 16 | let result = verify(¶ms, &mut proof_tmp_file); 17 | assert!(result.is_ok()); 18 | println!("proof is locally verified!"); 19 | }, 20 | } 21 | } -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/opts.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Debug, Parser)] 4 | #[clap(name = "zkwasm-circuit", version = "0.1.0")] 5 | pub struct Opts { 6 | #[clap(subcommand)] 7 | pub sub: Subcommands, 8 | } 9 | 10 | #[derive(Debug, Subcommand)] 11 | 12 | pub enum Subcommands { 13 | #[clap(name = "verify")] 14 | #[clap(about = "Locally verify proof.")] 15 | Verfiy { 16 | #[clap(long, short, value_name = "proof-file")] 17 | proof: String, 18 | }, 19 | } -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/prover/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod verifier; -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/prover/verifier.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use halo2_proofs::arithmetic::MultiMillerLoop; 3 | use halo2_proofs::plonk::{verify_proof, SingleVerifier, VerifyingKey}; 4 | use halo2_proofs::poly::commitment::{Params, ParamsVerifier}; 5 | use halo2aggregator_s::transcript::poseidon::PoseidonRead; 6 | 7 | pub struct ZKVerifier<'a, E: MultiMillerLoop> { 8 | params: Option<&'a Params>, 9 | vkey: Option>, 10 | } 11 | 12 | impl<'a, E: MultiMillerLoop> ZKVerifier<'a, E> { 13 | pub fn new() -> Self { 14 | Self { 15 | params: None, 16 | vkey: None, 17 | } 18 | } 19 | 20 | pub fn load_params(&mut self, params: &'a Params) { 21 | self.params = Some(params); 22 | } 23 | 24 | pub fn load_vkey(&mut self, vkey: &VerifyingKey) { 25 | self.vkey = Some(vkey.clone()); 26 | } 27 | 28 | pub fn verify_proof(&self, instances: &Vec, proof: &Vec) -> Result<()> { 29 | let params_verifier: ParamsVerifier = self 30 | .params 31 | .context("params not loaded")? 32 | .verifier(instances.len())?; 33 | let res = verify_proof( 34 | ¶ms_verifier, 35 | self.vkey.as_ref().context("vkey not loaded")?, 36 | SingleVerifier::new(¶ms_verifier), 37 | &[&[instances]], 38 | &mut PoseidonRead::init(&proof[..]), 39 | )?; 40 | Ok(res) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/types/instance.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use anyhow::{Ok, Result}; 4 | use data_encoding::BASE64; 5 | use halo2_proofs::{ 6 | arithmetic::{BaseExt, MultiMillerLoop}, 7 | pairing::group::ff::PrimeField, 8 | }; 9 | 10 | use super::JSONSerializer; 11 | 12 | #[derive(Clone)] 13 | pub struct Instances(pub Vec); 14 | 15 | impl JSONSerializer for Instances { 16 | type T = Self; 17 | 18 | fn from_json(ob: serde_json::Value) -> Result { 19 | let arr = ob.as_array().expect("the input is not an array"); 20 | let ins: Vec = arr 21 | .iter() 22 | .map(|val| { 23 | let str = val.as_str().expect("the input is not a string"); 24 | let bytes = BASE64 25 | .decode(str.as_bytes()) 26 | .expect("the input is not a base64 string"); 27 | E::Scalar::read(&mut bytes.as_slice()) 28 | .expect("the input is not a valid field element") 29 | }) 30 | .collect(); 31 | Ok(Instances(ins)) 32 | } 33 | 34 | fn to_json(&self) -> Result { 35 | let ob: Vec = self 36 | .0 37 | .iter() 38 | .map(|instance| { 39 | let mut bytes = vec![]; 40 | instance 41 | .write(&mut bytes) 42 | .expect("the input is not a valid field element"); 43 | BASE64.encode(&bytes) 44 | }) 45 | .collect(); 46 | Ok(serde_json::to_value(ob)?) 47 | } 48 | } 49 | 50 | impl fmt::Display for Instances { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | let arr: Vec = self 53 | .0 54 | .iter() 55 | .map(|ins| format!("{:x?}", ins.to_repr().as_ref())) 56 | .collect(); 57 | write!(f, "{:?}", arr) 58 | } 59 | } -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod params; 2 | pub mod proof; 3 | pub mod vkey; 4 | mod instance; 5 | 6 | use anyhow::Result; 7 | 8 | pub trait JSONSerializer { 9 | type T; 10 | fn from_json(ob: serde_json::Value) -> Result; 11 | fn to_json(&self) -> Result; 12 | } 13 | 14 | pub trait BinarySerializer { 15 | type T; 16 | fn from_binary(b: &[u8]) -> Result; 17 | fn to_binary(&self) -> Result>; 18 | } -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/types/params.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Ok, Result}; 2 | use halo2_proofs::arithmetic::CurveAffine; 3 | use halo2_proofs::poly::commitment::Params; 4 | 5 | use crate::types::BinarySerializer; 6 | 7 | impl BinarySerializer for Params { 8 | type T = Params; 9 | 10 | fn from_binary(b: &[u8]) -> Result { 11 | Ok(Params::::read(&b[..])?) 12 | } 13 | 14 | fn to_binary(&self) -> Result> { 15 | let mut ret = vec![]; 16 | self.write(&mut ret)?; 17 | Ok(ret) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/types/proof.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Ok, Result}; 2 | use data_encoding::BASE64; 3 | use halo2_proofs::pairing::bn256::Bn256; 4 | use serde_json::{json, Value}; 5 | 6 | use crate::types::instance::Instances; 7 | use crate::types::{BinarySerializer, JSONSerializer}; 8 | 9 | pub struct ProofData { 10 | pub proof: Vec, 11 | pub instances: Instances, 12 | pub vk: Vec, 13 | } 14 | 15 | impl BinarySerializer for ProofData { 16 | type T = Self; 17 | 18 | fn from_binary(buf: &[u8]) -> Result { 19 | let ob: Value = serde_json::from_slice(buf)?; 20 | let instances_ob = ob.get("instances").context("no instances")?.to_owned(); 21 | let proof = ob["proof"].as_str().context("no proof")?; 22 | let vk = ob["vk"].as_str().context("no proof")?; 23 | 24 | Ok(ProofData { 25 | proof: BASE64.decode(proof.as_bytes())?, 26 | instances: Instances::from_json(instances_ob)?, 27 | vk: BASE64.decode(vk.as_bytes())?, 28 | }) 29 | } 30 | 31 | fn to_binary(&self) -> Result> { 32 | let ob: Value = json!({ 33 | "proof": BASE64.encode(&self.proof), 34 | "instances": self.instances.to_json()?, 35 | "vk": BASE64.encode(&self.vk), 36 | }); 37 | Ok(serde_json::to_vec(&ob)?) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/zkwasm-circuit/src/types/vkey.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use delphinus_zkwasm::circuits::config::CircuitConfigure; 3 | use halo2_proofs::arithmetic::MultiMillerLoop; 4 | use halo2_proofs::plonk::{Circuit, VerifyingKey}; 5 | use halo2_proofs::poly::commitment::Params; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use super::BinarySerializer; 9 | 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct VkeyMetadata { 12 | vkey_data: Vec, 13 | circuit_cfg: CircuitConfigure, 14 | } 15 | 16 | impl VkeyMetadata { 17 | pub fn new( 18 | vkey: VerifyingKey, 19 | circuit_cfg: CircuitConfigure, 20 | ) -> Result { 21 | let mut ob = Self { 22 | vkey_data: vec![], 23 | circuit_cfg, 24 | }; 25 | vkey.write(&mut ob.vkey_data)?; 26 | Ok(ob) 27 | } 28 | 29 | pub fn load_vkey_with>( 30 | &self, 31 | params: &Params, 32 | ) -> Result> { 33 | self.circuit_cfg.clone().set_global_CIRCUIT_CONFIGURE(); 34 | let vkey = VerifyingKey::read::<_, C>(&mut &self.vkey_data[..], params) 35 | .context("failed to load verifying key")?; 36 | Ok(vkey) 37 | } 38 | } 39 | 40 | //TODO: optimize the serialization 41 | impl BinarySerializer for VkeyMetadata { 42 | type T = Self; 43 | 44 | fn from_binary(buf: &[u8]) -> Result { 45 | serde_json::from_slice(buf).context("failed to deserialize vkey metadata") 46 | } 47 | 48 | fn to_binary(&self) -> Result> { 49 | serde_json::to_vec(self).context("failed to serialize vkey metadata") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/prometheus/client_golang/prometheus" 6 | "github.com/prometheus/client_golang/prometheus/promhttp" 7 | ) 8 | 9 | var ( 10 | SyncedBlockHeightMtc = prometheus.NewGauge(prometheus.GaugeOpts{ // 11 | Name: "synced_block_height", 12 | Help: "Height of the latest synced block.", 13 | }) 14 | ProverMtc = prometheus.NewGauge(prometheus.GaugeOpts{ // 15 | Name: "prover_total", 16 | Help: "Total number of provers.", 17 | }) 18 | NewTaskMtc = prometheus.NewCounterVec( // 19 | prometheus.CounterOpts{ 20 | Name: "new_tasks_total", 21 | Help: "Total number of new tasks.", 22 | }, []string{"projectID"}) 23 | AssignedTaskMtc = prometheus.NewCounterVec( // 24 | prometheus.CounterOpts{ 25 | Name: "assigned_tasks_total", 26 | Help: "Total number of tasks that have been assigned.", 27 | }, []string{"projectID"}) 28 | FailedAssignedTaskMtc = prometheus.NewCounterVec( // 29 | prometheus.CounterOpts{ 30 | Name: "failed_assigned_tasks_total", 31 | Help: "Total number of tasks that have failed to be assigned.", 32 | }, []string{"projectID"}) 33 | TaskDurationMtc = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // 34 | Name: "task_duration_seconds", 35 | Help: "Duration of task execution in seconds.", 36 | }, []string{"projectID", "projectVersion", "taskID"}) 37 | FailedTaskNumMtc = prometheus.NewCounterVec( // 38 | prometheus.CounterOpts{ 39 | Name: "failed_tasks_total", 40 | Help: "Total number of tasks that have failed.", 41 | }, []string{"projectID"}) 42 | SucceedTaskNumMtc = prometheus.NewCounterVec( // 43 | prometheus.CounterOpts{ 44 | Name: "successful_tasks_total", 45 | Help: "Total number of tasks that have completed successfully.", 46 | }, []string{"projectID"}) 47 | ) 48 | 49 | func Init() { 50 | prometheus.MustRegister(SyncedBlockHeightMtc) 51 | prometheus.MustRegister(ProverMtc) 52 | prometheus.MustRegister(NewTaskMtc) 53 | prometheus.MustRegister(AssignedTaskMtc) 54 | prometheus.MustRegister(FailedAssignedTaskMtc) 55 | prometheus.MustRegister(TaskDurationMtc) 56 | prometheus.MustRegister(FailedTaskNumMtc) 57 | prometheus.MustRegister(SucceedTaskNumMtc) 58 | } 59 | 60 | // RegisterMetrics adds prometheus metrics endpoint to gin engine 61 | func RegisterMetrics(r *gin.Engine) { 62 | r.GET("/metrics", gin.WrapH(promhttp.Handler())) 63 | } 64 | -------------------------------------------------------------------------------- /project/manager.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/iotexproject/w3bstream/util/filefetcher" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | type ContractProject func(projectID string) (string, common.Hash, error) 12 | type ProjectFile func(projectID string) ([]byte, common.Hash, error) 13 | type UpsertProjectFile func(projectID string, file []byte, hash common.Hash) error 14 | 15 | type Manager struct { 16 | contractProject ContractProject 17 | projectFile ProjectFile 18 | upsertProjectFile UpsertProjectFile 19 | } 20 | 21 | func (m *Manager) Project(projectID string) (*Project, error) { 22 | uri, hash, err := m.contractProject(projectID) 23 | if err != nil { 24 | return nil, errors.Errorf("failed to get project metadata, project_id %v", projectID) 25 | } 26 | pf, fileHash, err := m.projectFile(projectID) 27 | if err != nil { 28 | return nil, errors.Errorf("failed to get project file, project_id %v", projectID) 29 | } 30 | 31 | p := &Project{} 32 | if bytes.Equal(fileHash[:], hash[:]) { 33 | err := p.Unmarshal(pf) 34 | return p, errors.Wrapf(err, "failed to unmarshal project file, project_id %v", projectID) 35 | } 36 | 37 | // project file hash mismatch, fetch new project file from uri 38 | fetcher := &filefetcher.Filedescriptor{Uri: uri, Hash: hash} 39 | data, err := fetcher.FetchFile() 40 | if err != nil { 41 | return nil, errors.Wrapf(err, "failed to fetch project file, project_id %v", projectID) 42 | } 43 | err = p.Unmarshal(data) 44 | if err != nil { 45 | return nil, errors.Wrapf(err, "failed to unmarshal project file, project_id %v", projectID) 46 | } 47 | err = m.upsertProjectFile(projectID, data, hash) 48 | return p, err 49 | } 50 | 51 | func NewManager(cp ContractProject, pf ProjectFile, upf UpsertProjectFile) *Manager { 52 | return &Manager{ 53 | contractProject: cp, 54 | projectFile: pf, 55 | upsertProjectFile: upf, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /project/project.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | errEmptyConfig = errors.New("config is empty") 11 | errEmptyCode = errors.New("code is empty") 12 | ) 13 | 14 | // TODO: prefer protobuf for serialization and deserialization 15 | type Project struct { 16 | DefaultVersion string `json:"defaultVersion,omitempty"` 17 | Configs []*Config `json:"config"` 18 | } 19 | 20 | type Attribute struct { 21 | Paused bool 22 | RequestedProverAmount uint64 23 | } 24 | 25 | type SignedKey struct { 26 | Name string `json:"name"` 27 | Type string `json:"type"` 28 | } 29 | 30 | type Config struct { 31 | Version string `json:"version"` 32 | VMTypeID uint64 `json:"vmTypeID"` 33 | ProofType string `json:"proofType"` // liveness, movement 34 | SignedKeys []SignedKey `json:"signedKeys"` 35 | SignatureAlgorithm string `json:"signatureAlgorithm"` 36 | HashAlgorithm string `json:"hashAlgorithm"` 37 | TaskProcessingBatch uint64 `json:"taskProcessingBatch"` 38 | Metadata string `json:"metadata,omitempty"` 39 | MetadataHash string `json:"metadataHash,omitempty"` 40 | Code string `json:"code"` 41 | CodeHash string `json:"codeHash,omitempty"` 42 | } 43 | 44 | func (p *Project) Config(version string) (*Config, error) { 45 | if len(version) == 0 { 46 | return p.DefaultConfig() 47 | } 48 | for _, c := range p.Configs { 49 | if c.Version == version { 50 | return c, nil 51 | } 52 | } 53 | return nil, errors.New("project config not exist") 54 | } 55 | 56 | func (p *Project) DefaultConfig() (*Config, error) { 57 | if len(p.DefaultVersion) > 0 { 58 | return p.Config(p.DefaultVersion) 59 | } 60 | return p.Configs[0], nil 61 | } 62 | 63 | func (p *Project) Marshal() ([]byte, error) { 64 | return json.Marshal(p) 65 | } 66 | 67 | func (p *Project) Unmarshal(data []byte) error { 68 | if err := json.Unmarshal(data, p); err != nil { 69 | return errors.Wrap(err, "failed to unmarshal project") 70 | } 71 | 72 | if len(p.Configs) == 0 { 73 | return errEmptyConfig 74 | } 75 | for _, c := range p.Configs { 76 | if err := c.validate(); err != nil { 77 | return err 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | func (c *Config) validate() error { 84 | if len(c.Code) == 0 { 85 | return errEmptyCode 86 | } 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | ### Install the dependency 2 | 3 | ``` bash 4 | pip3 install ipfshttpclient 5 | ``` 6 | 7 | ### Start 8 | 9 | ``` bash 10 | python3 compress_file.py "code file" 11 | ``` 12 | 13 | *Note* If you encounter an "Unsupported daemon version" issue, please refer to [#329](https://github.com/ipfs-shipyard/py-ipfs-http-client/issues/329) 14 | -------------------------------------------------------------------------------- /scripts/compress_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | import zlib 3 | import binascii 4 | import ipfshttpclient 5 | import sys 6 | 7 | dns_endpoint = 'ipfs.mainnet.iotex.io' 8 | def convert_code_to_zlib_hex(code_file: str) -> str: 9 | try: 10 | with open(code_file, 'rb') as f: 11 | content = f.read() 12 | 13 | compressed_data = zlib.compress(content) 14 | 15 | hex_string = binascii.hexlify(compressed_data).decode('utf-8') 16 | 17 | return hex_string 18 | except Exception as e: 19 | raise RuntimeError(f"Failed to convert and compress the code file {code_file}") from e 20 | 21 | def upload_to_ipfs(hex_string: str, endpoint: str = f"/dns/{dns_endpoint}/tcp/443/https") -> str: 22 | try: 23 | client = ipfshttpclient.connect(endpoint) 24 | 25 | res = client.add_bytes(hex_string.encode('utf-8')) 26 | cid = res 27 | 28 | client.pin.add(cid) 29 | 30 | return f"ipfs://{dns_endpoint}/{cid}" 31 | except Exception as e: 32 | raise RuntimeError(f"Failed to upload file to IPFS: {e}") 33 | 34 | if __name__ == '__main__': 35 | if len(sys.argv) != 2: 36 | print("Usage: python3 compress_file.py ") 37 | sys.exit(1) 38 | 39 | code_file = sys.argv[1] 40 | 41 | # Convert code file to zlib compressed hex string 42 | hex_string = convert_code_to_zlib_hex(code_file) 43 | print(f"Zlib Hex String: {hex_string}") 44 | 45 | # Upload to IPFS 46 | ipfs_hash = upload_to_ipfs(hex_string) 47 | print(f"IPFS Hash: {ipfs_hash}") -------------------------------------------------------------------------------- /scripts/init_risc0server.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE vms ( 2 | id SERIAL PRIMARY KEY, 3 | project_name VARCHAR NOT NULL, 4 | elf TEXT NOT NULL, 5 | image_id VARCHAR NOT NULL 6 | ); 7 | 8 | CREATE TABLE proofs ( 9 | id SERIAL PRIMARY KEY, 10 | image_id VARCHAR NOT NULL, 11 | private_input VARCHAR NOT NULL, 12 | public_input VARCHAR NOT NULL, 13 | receipt_type VARCHAR NOT NULL, 14 | receipt TEXT, 15 | status VARCHAR NOT NULL, 16 | create_at TIMESTAMP NOT NULL DEFAULT now() 17 | ); 18 | -------------------------------------------------------------------------------- /scripts/start_compress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pip3 install ipfshttpclient 4 | 5 | python3 compress_file.py $1 -------------------------------------------------------------------------------- /service/apinode/aggregator/aggregator.go: -------------------------------------------------------------------------------- 1 | package aggregator 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "log/slog" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/iotexproject/w3bstream/project" 14 | apidb "github.com/iotexproject/w3bstream/service/apinode/db" 15 | "github.com/iotexproject/w3bstream/service/sequencer/api" 16 | "github.com/pkg/errors" 17 | ) 18 | 19 | func Run(projectManager *project.Manager, db *apidb.DB, sequencerAddr string, interval time.Duration) { 20 | ticker := time.NewTicker(interval) 21 | for range ticker.C { 22 | ts, err := db.FetchAllTask() 23 | if err != nil { 24 | slog.Error("failed to fetch all tasks", "error", err) 25 | continue 26 | } 27 | if len(ts) == 0 { 28 | continue 29 | } 30 | 31 | taskMap := make(map[string][]*apidb.Task) 32 | for i := range ts { 33 | k := ts[i].ProjectID + "_" + ts[i].DevicePubKey 34 | taskMap[k] = append(taskMap[k], ts[i]) 35 | } 36 | 37 | for _, tasks := range taskMap { 38 | pid := tasks[0].ProjectID 39 | p, err := projectManager.Project(pid) 40 | if err != nil { 41 | slog.Error("failed to get project", "error", err, "project_id", pid) 42 | continue 43 | } 44 | // TODO support project config 45 | cfg, err := p.DefaultConfig() 46 | if err != nil { 47 | slog.Error("failed to get project config", "error", err, "project_id", pid) 48 | continue 49 | } 50 | if cfg.ProofType == "movement" { 51 | prevTaskID := tasks[0].TaskID 52 | tasks[len(tasks)-1].PrevTaskID = prevTaskID 53 | } 54 | } 55 | 56 | if err := dumpTasks(db, ts); err != nil { 57 | slog.Error("failed to dump tasks", "error", err) 58 | continue 59 | } 60 | 61 | for _, tasks := range taskMap { 62 | lastTask := tasks[len(tasks)-1] 63 | if err := notify(sequencerAddr, common.HexToHash(lastTask.TaskID)); err != nil { 64 | slog.Error("failed to notify sequencer", "error", err) 65 | continue 66 | } 67 | } 68 | } 69 | } 70 | 71 | func dumpTasks(db *apidb.DB, ts []*apidb.Task) error { 72 | // add tasks to remote 73 | if err := db.CreateTasks(ts); err != nil { 74 | slog.Error("failed to create tasks", "error", err) 75 | return err 76 | } 77 | // remove tasks from local 78 | if err := db.DeleteTasks(ts); err != nil { 79 | slog.Error("failed to delete tasks at local", "error", err) 80 | return err 81 | } 82 | return nil 83 | } 84 | 85 | func notify(sequencerAddr string, taskID common.Hash) error { 86 | reqSequencer := &api.CreateTaskReq{TaskID: taskID} 87 | reqSequencerJ, err := json.Marshal(reqSequencer) 88 | if err != nil { 89 | return errors.Wrap(err, "failed to marshal sequencer request") 90 | } 91 | resp, err := http.Post(fmt.Sprintf("http://%s/task", sequencerAddr), "application/json", bytes.NewBuffer(reqSequencerJ)) 92 | if err != nil { 93 | return errors.Wrap(err, "failed to call sequencer service") 94 | } 95 | defer resp.Body.Close() 96 | 97 | if resp.StatusCode != http.StatusOK { 98 | body, err := io.ReadAll(resp.Body) 99 | if err == nil { 100 | err = errors.New(string(body)) 101 | } 102 | return errors.Wrap(err, "failed to call sequencer service") 103 | } 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /service/apinode/apinode.go: -------------------------------------------------------------------------------- 1 | package apinode 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/iotexproject/w3bstream/monitor" 9 | "github.com/iotexproject/w3bstream/project" 10 | "github.com/iotexproject/w3bstream/service/apinode/aggregator" 11 | "github.com/iotexproject/w3bstream/service/apinode/api" 12 | "github.com/iotexproject/w3bstream/service/apinode/config" 13 | "github.com/iotexproject/w3bstream/service/apinode/db" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | type APINode struct { 18 | cfg *config.Config 19 | db *db.DB 20 | } 21 | 22 | func NewAPINode(cfg *config.Config, db *db.DB) *APINode { 23 | return &APINode{ 24 | cfg: cfg, 25 | db: db, 26 | } 27 | } 28 | 29 | func (n *APINode) Start() error { 30 | if err := monitor.Run( 31 | &monitor.Handler{ 32 | ScannedBlockNumber: n.db.ScannedBlockNumber, 33 | UpsertScannedBlockNumber: n.db.UpsertScannedBlockNumber, 34 | AssignTask: n.db.UpsertAssignedTask, 35 | SettleTask: n.db.UpsertSettledTask, 36 | UpsertProjectDevice: n.db.UpsertProjectDevice, 37 | UpsertProject: n.db.UpsertProject, 38 | }, 39 | &monitor.ContractAddr{ 40 | Project: common.HexToAddress(n.cfg.ProjectContractAddr), 41 | TaskManager: common.HexToAddress(n.cfg.TaskManagerContractAddr), 42 | IoID: common.HexToAddress(n.cfg.IoIDContractAddr), 43 | }, 44 | n.cfg.BeginningBlockNumber, 45 | n.cfg.ChainEndpoint, 46 | ); err != nil { 47 | return errors.Wrap(err, "failed to run contract monitor") 48 | } 49 | 50 | projectManager := project.NewManager(n.db.Project, n.db.ProjectFile, n.db.UpsertProjectFile) 51 | 52 | go aggregator.Run(projectManager, n.db, n.cfg.SequencerServiceEndpoint, time.Duration(n.cfg.TaskAggregatorIntervalSecond)*time.Second) 53 | 54 | go func() { 55 | if err := api.Run(n.db, projectManager, n.cfg.ServiceEndpoint, n.cfg.SequencerServiceEndpoint, n.cfg.ProverServiceEndpoint); err != nil { 56 | log.Fatal(err) 57 | } 58 | }() 59 | 60 | return nil 61 | } 62 | 63 | func (n *APINode) Stop() error { 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /service/apinode/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | 7 | "github.com/iotexproject/w3bstream/util/env" 8 | ) 9 | 10 | type Config struct { 11 | LogLevel slog.Level `env:"LOG_LEVEL,optional"` 12 | ServiceEndpoint string `env:"HTTP_SERVICE_ENDPOINT"` 13 | SequencerServiceEndpoint string `env:"SEQUENCER_SERVICE_ENDPOINT"` 14 | TaskAggregatorIntervalSecond uint64 `env:"TASK_AGGREGATOR_INTERVAL_SECOND,optional"` 15 | ProverServiceEndpoint string `env:"PROVER_SERVICE_ENDPOINT"` 16 | DatabaseDSN string `env:"DATABASE_DSN"` 17 | ChainEndpoint string `env:"CHAIN_ENDPOINT,optional"` 18 | BeginningBlockNumber uint64 `env:"BEGINNING_BLOCK_NUMBER,optional"` 19 | ProjectContractAddr string `env:"PROJECT_CONTRACT_ADDRESS,optional"` 20 | TaskManagerContractAddr string `env:"TASK_MANAGER_CONTRACT_ADDRESS,optional"` 21 | IoIDContractAddr string `env:"IOID_CONTRACT_ADDRESS,optional"` 22 | LocalDBDir string `env:"LOCAL_DB_DIRECTORY,optional"` 23 | env string `env:"-"` 24 | } 25 | 26 | var defaultTestnetConfig = &Config{ 27 | LogLevel: slog.LevelInfo, 28 | ServiceEndpoint: ":9000", 29 | SequencerServiceEndpoint: "localhost:9001", 30 | TaskAggregatorIntervalSecond: 1, 31 | ProverServiceEndpoint: "localhost:9002", 32 | ChainEndpoint: "https://babel-api.testnet.iotex.io", 33 | BeginningBlockNumber: 28685000, 34 | TaskManagerContractAddr: "0xF0714400a4C0C72007A9F910C5E3007614958636", 35 | IoIDContractAddr: "0x45Ce3E6f526e597628c73B731a3e9Af7Fc32f5b7", 36 | LocalDBDir: "./local_db", 37 | env: "TESTNET", 38 | } 39 | 40 | var defaultMainnetConfig = &Config{ 41 | LogLevel: slog.LevelInfo, 42 | ServiceEndpoint: ":9000", 43 | SequencerServiceEndpoint: "localhost:9001", 44 | TaskAggregatorIntervalSecond: 300, 45 | ProverServiceEndpoint: "localhost:9002", 46 | ChainEndpoint: "https://babel-api.mainnet.iotex.io", 47 | BeginningBlockNumber: 31780000, 48 | TaskManagerContractAddr: "0x0A422759A8c6b22Ae8B9C4364763b614d5c0CD29", 49 | IoIDContractAddr: "0x1FCB980eD0287777ab05ADc93012332e11300e54", 50 | LocalDBDir: "./local_db", 51 | env: "MAINNET", 52 | } 53 | 54 | func (c *Config) init() error { 55 | if err := env.ParseEnv(c); err != nil { 56 | return err 57 | } 58 | h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(c.LogLevel)}) 59 | slog.SetDefault(slog.New(h)) 60 | return nil 61 | } 62 | 63 | func Get() (*Config, error) { 64 | var conf *Config 65 | env := os.Getenv("ENV") 66 | switch env { 67 | case "TESTNET": 68 | conf = defaultTestnetConfig 69 | case "MAINNET": 70 | conf = defaultMainnetConfig 71 | default: 72 | env = "TESTNET" 73 | conf = defaultTestnetConfig 74 | } 75 | conf.env = env 76 | if err := conf.init(); err != nil { 77 | return nil, err 78 | } 79 | return conf, nil 80 | } 81 | 82 | func (c *Config) Print() { 83 | env.Print(c) 84 | } 85 | -------------------------------------------------------------------------------- /service/apinode/db/clickhouse.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/ClickHouse/clickhouse-go/v2" 8 | "github.com/ClickHouse/clickhouse-go/v2/lib/driver" 9 | "github.com/ethereum/go-ethereum/common" 10 | 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | type Task struct { 15 | TaskID string `ch:"task_id" gorm:"primarykey"` 16 | DevicePubKey string `ch:"device_public_key" gorm:"not null"` 17 | Nonce uint64 `ch:"nonce" gorm:"not null"` 18 | ProjectID string `ch:"project_id" gorm:"not null"` 19 | ProjectVersion string `ch:"project_version" gorm:"not null"` 20 | Payload string `ch:"payload" gorm:"not null"` 21 | Signature string `ch:"signature" gorm:"not null"` 22 | SignatureAlgorithm string `ch:"signature_algorithm" gorm:"not null"` 23 | HashAlgorithm string `ch:"hash_algorithm" gorm:"not null"` 24 | PayloadHash string `ch:"payload_hash" gorm:"not null"` 25 | TaskHash string `ch:"task_hash" gorm:"not null"` 26 | CreatedAt time.Time `ch:"created_at" gorm:"not null"` 27 | PrevTaskID string `ch:"previous_task_id" gorm:"not null"` 28 | } 29 | 30 | func (p *DB) CreateTasks(ts []*Task) error { 31 | batch, err := p.ch.PrepareBatch(context.Background(), "INSERT INTO w3bstream_tasks") 32 | if err != nil { 33 | return errors.Wrap(err, "failed to prepare batch") 34 | } 35 | for _, t := range ts { 36 | if err := batch.AppendStruct(t); err != nil { 37 | return errors.Wrap(err, "failed to append struct") 38 | } 39 | } 40 | if err := batch.Send(); err != nil { 41 | return errors.Wrap(err, "failed to create tasks") 42 | } 43 | time.Sleep(1 * time.Second) // after writing to clickhouse, reading immediately will not return the value. 44 | return nil 45 | } 46 | 47 | func (p *DB) FetchTask(taskID common.Hash) (*Task, error) { 48 | t := Task{} 49 | if err := p.ch.QueryRow(context.Background(), "SELECT * FROM w3bstream_tasks WHERE task_id = ?", taskID.Hex()).ScanStruct(&t); err != nil { 50 | return nil, errors.Wrap(err, "failed to query task") 51 | } 52 | return &t, nil 53 | } 54 | 55 | func migrateCH(conn driver.Conn) error { 56 | err := conn.Exec(context.Background(), ` 57 | CREATE TABLE IF NOT EXISTS w3bstream_tasks 58 | ( 59 | task_id String NOT NULL, 60 | device_public_key String NOT NULL, 61 | nonce UInt64 NOT NULL, 62 | project_id String NOT NULL, 63 | project_version String NOT NULL, 64 | payload String NOT NULL, 65 | signature String NOT NULL, 66 | signature_algorithm String NOT NULL, 67 | hash_algorithm String NOT NULL, 68 | payload_hash String NOT NULL, 69 | task_hash String NOT NULL, 70 | created_at DateTime NOT NULL, 71 | previous_task_id String NOT NULL 72 | ) 73 | ENGINE = ReplacingMergeTree() 74 | PRIMARY KEY task_id 75 | ORDER BY task_id`, 76 | ) 77 | return errors.Wrap(err, "failed to create clickhouse table") 78 | } 79 | 80 | func newCH(dsn string) (driver.Conn, error) { 81 | op, err := clickhouse.ParseDSN(dsn) 82 | if err != nil { 83 | return nil, errors.Wrap(err, "failed to parse clickhouse dsn") 84 | } 85 | conn, err := clickhouse.Open(op) 86 | if err != nil { 87 | return nil, errors.Wrap(err, "failed to connect clickhouse") 88 | } 89 | if err := migrateCH(conn); err != nil { 90 | return nil, err 91 | } 92 | return conn, nil 93 | } 94 | -------------------------------------------------------------------------------- /service/apinode/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/ClickHouse/clickhouse-go/v2/lib/driver" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | // TODO: remove this combinator 10 | type DB struct { 11 | sqlite *gorm.DB 12 | ch driver.Conn 13 | } 14 | 15 | func New(localDBDir, dsn string) (*DB, error) { 16 | sqlite, err := newSqlite(localDBDir) 17 | if err != nil { 18 | return nil, err 19 | } 20 | ch, err := newCH(dsn) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &DB{sqlite: sqlite, ch: ch}, nil 25 | } 26 | -------------------------------------------------------------------------------- /service/prover/api/http.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log/slog" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/gin-gonic/gin" 10 | "github.com/pkg/errors" 11 | 12 | "github.com/iotexproject/w3bstream/metrics" 13 | "github.com/iotexproject/w3bstream/service/prover/db" 14 | ) 15 | 16 | type errResp struct { 17 | Error string `json:"error,omitempty"` 18 | } 19 | 20 | func newErrResp(err error) *errResp { 21 | return &errResp{Error: err.Error()} 22 | } 23 | 24 | type QueryTaskResp struct { 25 | Time time.Time `json:"time"` 26 | Processed bool `json:"processed"` 27 | Error string `json:"error,omitempty"` 28 | } 29 | 30 | type httpServer struct { 31 | engine *gin.Engine 32 | db *db.DB 33 | } 34 | 35 | func (s *httpServer) queryTask(c *gin.Context) { 36 | taskIDStr := c.Param("id") 37 | taskID := common.HexToHash(taskIDStr) 38 | 39 | processed, errMsg, createdAt, err := s.db.ProcessedTask(taskID) 40 | if err != nil { 41 | slog.Error("failed to query processed task", "error", err) 42 | c.JSON(http.StatusInternalServerError, newErrResp(errors.Wrap(err, "failed to query processed task"))) 43 | return 44 | } 45 | 46 | c.JSON(http.StatusOK, &QueryTaskResp{ 47 | Time: createdAt, 48 | Processed: processed, 49 | Error: errMsg, 50 | }) 51 | } 52 | 53 | // this func will block caller 54 | func Run(db *db.DB, address string) error { 55 | s := &httpServer{ 56 | engine: gin.Default(), 57 | db: db, 58 | } 59 | 60 | s.engine.GET("/task/:id", s.queryTask) 61 | metrics.RegisterMetrics(s.engine) 62 | 63 | if err := s.engine.Run(address); err != nil { 64 | slog.Error("failed to start http server", "address", address, "error", err) 65 | return errors.Wrap(err, "could not start http server; check if the address is in use or network is accessible") 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /service/prover/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | 7 | "github.com/iotexproject/w3bstream/util/env" 8 | ) 9 | 10 | type Config struct { 11 | LogLevel slog.Level `env:"LOG_LEVEL,optional"` 12 | ServiceEndpoint string `env:"HTTP_SERVICE_ENDPOINT"` 13 | VMEndpoints string `env:"VM_ENDPOINTS"` 14 | DatasourceDSN string `env:"DATASOURCE_DSN"` 15 | ChainEndpoint string `env:"CHAIN_ENDPOINT,optional"` 16 | ProjectContractAddr string `env:"PROJECT_CONTRACT_ADDRESS,optional"` 17 | RouterContractAddr string `env:"ROUTER_CONTRACT_ADDRESS,optional"` 18 | TaskManagerContractAddr string `env:"TASK_MANAGER_CONTRACT_ADDRESS,optional"` 19 | ProverPrvKey string `env:"PROVER_OPERATOR_PRIVATE_KEY,optional"` 20 | BeginningBlockNumber uint64 `env:"BEGINNING_BLOCK_NUMBER,optional"` 21 | LocalDBDir string `env:"LOCAL_DB_DIRECTORY,optional"` 22 | env string `env:"-"` 23 | } 24 | 25 | var ( 26 | defaultTestnetConfig = &Config{ 27 | LogLevel: slog.LevelInfo, 28 | ServiceEndpoint: ":9002", 29 | VMEndpoints: `{"1":"localhost:4001","2":"localhost:4002","3":"zkwasm:4001","4":"wasm:4001"}`, 30 | ChainEndpoint: "https://babel-api.testnet.iotex.io", 31 | ProjectContractAddr: "0x0abec44FC786e8da12267Db5fdeB4311AD1A0A8A", 32 | RouterContractAddr: "0x28E0A99A76a467E7418019cBBbF79E4599C73B5B", 33 | TaskManagerContractAddr: "0xF0714400a4C0C72007A9F910C5E3007614958636", 34 | ProverPrvKey: "a5f4e99aa80342d5451e8f8fd0dc357ccddb70d3827428fb1fc366f70833497f", 35 | BeginningBlockNumber: 28685000, 36 | LocalDBDir: "./local_db", 37 | env: "TESTNET", 38 | } 39 | defaultMainnetConfig = &Config{ 40 | LogLevel: slog.LevelInfo, 41 | ServiceEndpoint: ":9002", 42 | VMEndpoints: `{"1":"localhost:4001","2":"localhost:4002","3":"zkwasm:4001","4":"wasm:4001"}`, 43 | ChainEndpoint: "https://babel-api.mainnet.iotex.io", 44 | ProjectContractAddr: "0x6EF4559f2023C93F78d27E0151deF083638478d2", 45 | RouterContractAddr: "0x580D9686A7A188746B9f4a06fb5ec9e14E937fde", 46 | TaskManagerContractAddr: "0x0A422759A8c6b22Ae8B9C4364763b614d5c0CD29", 47 | ProverPrvKey: "a5f4e99aa80342d5451e8f8fd0dc357ccddb70d3827428fb1fc366f70833497f", 48 | BeginningBlockNumber: 31780000, 49 | LocalDBDir: "./local_db", 50 | env: "MAINNET", 51 | } 52 | ) 53 | 54 | func (c *Config) init() error { 55 | if err := env.ParseEnv(c); err != nil { 56 | return err 57 | } 58 | h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(c.LogLevel)}) 59 | slog.SetDefault(slog.New(h)) 60 | return nil 61 | } 62 | 63 | func Get() (*Config, error) { 64 | var conf *Config 65 | env := os.Getenv("ENV") 66 | switch env { 67 | case "TESTNET": 68 | conf = defaultTestnetConfig 69 | case "MAINNET": 70 | conf = defaultMainnetConfig 71 | default: 72 | env = "TESTNET" 73 | conf = defaultTestnetConfig 74 | } 75 | conf.env = env 76 | if err := conf.init(); err != nil { 77 | return nil, err 78 | } 79 | return conf, nil 80 | } 81 | 82 | func (c *Config) Print() { 83 | env.Print(c) 84 | } 85 | -------------------------------------------------------------------------------- /service/prover/prover.go: -------------------------------------------------------------------------------- 1 | package prover 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "encoding/json" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/pkg/errors" 10 | 11 | "github.com/iotexproject/w3bstream/datasource" 12 | "github.com/iotexproject/w3bstream/monitor" 13 | "github.com/iotexproject/w3bstream/project" 14 | "github.com/iotexproject/w3bstream/service/prover/api" 15 | "github.com/iotexproject/w3bstream/service/prover/config" 16 | "github.com/iotexproject/w3bstream/service/prover/db" 17 | "github.com/iotexproject/w3bstream/task/processor" 18 | "github.com/iotexproject/w3bstream/vm" 19 | ) 20 | 21 | type Prover struct { 22 | db *db.DB 23 | cfg *config.Config 24 | prv *ecdsa.PrivateKey 25 | vmHandler *vm.Handler 26 | } 27 | 28 | func NewProver(cfg *config.Config, db *db.DB, privateKey *ecdsa.PrivateKey) *Prover { 29 | vmEndpoints := map[uint64]string{} 30 | if err := json.Unmarshal([]byte(cfg.VMEndpoints), &vmEndpoints); err != nil { 31 | log.Fatal(errors.Wrap(err, "failed to unmarshal vm endpoints")) 32 | } 33 | vmHandler, err := vm.NewHandler(vmEndpoints) 34 | if err != nil { 35 | log.Fatal(errors.Wrap(err, "failed to new vm handler")) 36 | } 37 | return &Prover{ 38 | cfg: cfg, 39 | vmHandler: vmHandler, 40 | prv: privateKey, 41 | db: db, 42 | } 43 | } 44 | 45 | func (p *Prover) Start() error { 46 | if err := monitor.Run( 47 | &monitor.Handler{ 48 | ScannedBlockNumber: p.db.ScannedBlockNumber, 49 | UpsertScannedBlockNumber: p.db.UpsertScannedBlockNumber, 50 | AssignTask: p.db.CreateTask, 51 | UpsertProject: p.db.UpsertProject, 52 | SettleTask: p.db.DeleteTask, 53 | }, 54 | &monitor.ContractAddr{ 55 | Project: common.HexToAddress(p.cfg.ProjectContractAddr), 56 | TaskManager: common.HexToAddress(p.cfg.TaskManagerContractAddr), 57 | }, 58 | p.cfg.BeginningBlockNumber, 59 | p.cfg.ChainEndpoint, 60 | ); err != nil { 61 | return errors.Wrap(err, "failed to run monitor") 62 | } 63 | 64 | projectManager := project.NewManager(p.db.Project, p.db.ProjectFile, p.db.UpsertProjectFile) 65 | 66 | datasource, err := datasource.NewClickhouse(p.cfg.DatasourceDSN) 67 | if err != nil { 68 | return errors.Wrap(err, "failed to new datasource") 69 | } 70 | 71 | if err := processor.Run(p.vmHandler.Handle, projectManager.Project, p.db, datasource.Retrieve, p.prv, p.cfg.ChainEndpoint, common.HexToAddress(p.cfg.RouterContractAddr)); err != nil { 72 | return errors.Wrap(err, "failed to run task processor") 73 | } 74 | 75 | go func() { 76 | if err := api.Run(p.db, p.cfg.ServiceEndpoint); err != nil { 77 | log.Fatal(err) 78 | } 79 | }() 80 | 81 | return nil 82 | } 83 | 84 | func (p *Prover) Stop() error { 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /service/sequencer/api/http.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log/slog" 5 | "net/http" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/gin-gonic/gin" 9 | "github.com/pkg/errors" 10 | 11 | "github.com/iotexproject/w3bstream/metrics" 12 | "github.com/iotexproject/w3bstream/service/sequencer/db" 13 | "github.com/iotexproject/w3bstream/task" 14 | ) 15 | 16 | type RetrieveTask func(taskIDs []common.Hash) ([]*task.Task, error) 17 | 18 | type CreateTaskReq struct { 19 | TaskID common.Hash `json:"taskID" binding:"required"` 20 | } 21 | 22 | type errResp struct { 23 | Error string `json:"error,omitempty"` 24 | } 25 | 26 | func newErrResp(err error) *errResp { 27 | return &errResp{Error: err.Error()} 28 | } 29 | 30 | type httpServer struct { 31 | engine *gin.Engine 32 | db *db.DB 33 | retrieve RetrieveTask 34 | } 35 | 36 | func (s *httpServer) createTask(c *gin.Context) { 37 | req := &CreateTaskReq{} 38 | if err := c.ShouldBindJSON(req); err != nil { 39 | slog.Error("failed to bind request", "error", err) 40 | c.JSON(http.StatusBadRequest, newErrResp(errors.Wrap(err, "invalid request payload"))) 41 | return 42 | } 43 | 44 | ts, err := s.retrieve([]common.Hash{req.TaskID}) 45 | if err != nil { 46 | slog.Error("failed to retrieve task", "error", err) 47 | c.JSON(http.StatusBadRequest, newErrResp(errors.Wrap(err, "failed to retrieve task"))) 48 | return 49 | } 50 | 51 | t := ts[0] 52 | metrics.NewTaskMtc.WithLabelValues(t.ProjectID.String()).Inc() 53 | 54 | if err := s.db.CreateTask(req.TaskID); err != nil { 55 | slog.Error("failed to create task", "error", err) 56 | c.JSON(http.StatusInternalServerError, newErrResp(errors.Wrap(err, "failed to create task"))) 57 | return 58 | } 59 | 60 | slog.Info("get a new task", "project_id", t.ProjectID, "task_id", req.TaskID.String()) 61 | c.Status(http.StatusOK) 62 | } 63 | 64 | // this func will block caller 65 | func Run(address string, db *db.DB, retrieve RetrieveTask) error { 66 | s := &httpServer{ 67 | retrieve: retrieve, 68 | db: db, 69 | engine: gin.Default(), 70 | } 71 | 72 | s.engine.POST("/task", s.createTask) 73 | metrics.RegisterMetrics(s.engine) 74 | 75 | if err := s.engine.Run(address); err != nil { 76 | slog.Error("failed to start http server", "address", address, "error", err) 77 | return errors.Wrap(err, "could not start http server; check if the address is in use or network is accessible") 78 | } 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /service/sequencer/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | 7 | "github.com/iotexproject/w3bstream/util/env" 8 | ) 9 | 10 | type Config struct { 11 | LogLevel slog.Level `env:"LOG_LEVEL,optional"` 12 | ServiceEndpoint string `env:"HTTP_SERVICE_ENDPOINT"` 13 | TaskProcessingBandwidth int `env:"TASK_PROCESSING_BANDWIDTH"` 14 | DatasourceDSN string `env:"DATASOURCE_DSN"` 15 | ChainEndpoint string `env:"CHAIN_ENDPOINT,optional"` 16 | OperatorPrvKey string `env:"OPERATOR_PRIVATE_KEY,optional"` 17 | LocalDBDir string `env:"LOCAL_DB_DIRECTORY,optional"` 18 | BeginningBlockNumber uint64 `env:"BEGINNING_BLOCK_NUMBER,optional"` 19 | ProverContractAddr string `env:"PROVER_CONTRACT_ADDRESS,optional"` 20 | MinterContractAddr string `env:"MINTER_CONTRACT_ADDRESS,optional"` 21 | TaskManagerContractAddr string `env:"TASK_MANAGER_CONTRACT_ADDRESS,optional"` 22 | env string `env:"-"` 23 | } 24 | 25 | var ( 26 | defaultTestnetConfig = &Config{ 27 | LogLevel: slog.LevelInfo, 28 | ServiceEndpoint: ":9001", 29 | TaskProcessingBandwidth: 20, 30 | ChainEndpoint: "https://babel-api.testnet.iotex.io", 31 | OperatorPrvKey: "33e6ba3e033131026903f34dfa208feb88c284880530cf76280b68d38041c67b", 32 | ProverContractAddr: "0xab6836908d15E42D30bdEf14cbFA4ad45dCAF3a3", 33 | MinterContractAddr: "0x49C096AE869A3054Db06ffF221b917b41f94CEf3", 34 | TaskManagerContractAddr: "0xF0714400a4C0C72007A9F910C5E3007614958636", 35 | LocalDBDir: "./local_db", 36 | BeginningBlockNumber: 28685000, 37 | env: "TESTNET", 38 | } 39 | defaultMainnetConfig = &Config{ 40 | LogLevel: slog.LevelInfo, 41 | ServiceEndpoint: ":9001", 42 | TaskProcessingBandwidth: 20, 43 | ChainEndpoint: "https://babel-api.mainnet.iotex.io", 44 | OperatorPrvKey: "33e6ba3e033131026903f34dfa208feb88c284880530cf76280b68d38041c67b", 45 | ProverContractAddr: "0x73aE62021517685c4b8Db4422968BCEc80F84063", 46 | MinterContractAddr: "0x270bF4c2d0269f8Bf1c2187c20177DCc2f15C3C9", 47 | TaskManagerContractAddr: "0x0A422759A8c6b22Ae8B9C4364763b614d5c0CD29", 48 | LocalDBDir: "./local_db", 49 | BeginningBlockNumber: 31780000, 50 | env: "MAINNET", 51 | } 52 | ) 53 | 54 | func (c *Config) init() error { 55 | if err := env.ParseEnv(c); err != nil { 56 | return err 57 | } 58 | h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(c.LogLevel)}) 59 | slog.SetDefault(slog.New(h)) 60 | return nil 61 | } 62 | 63 | func Get() (*Config, error) { 64 | var conf *Config 65 | env := os.Getenv("ENV") 66 | switch env { 67 | case "TESTNET": 68 | conf = defaultTestnetConfig 69 | case "MAINNET": 70 | conf = defaultMainnetConfig 71 | default: 72 | env = "TESTNET" 73 | conf = defaultTestnetConfig 74 | } 75 | conf.env = env 76 | if err := conf.init(); err != nil { 77 | return nil, err 78 | } 79 | return conf, nil 80 | } 81 | 82 | func (c *Config) Print() { 83 | env.Print(c) 84 | } 85 | -------------------------------------------------------------------------------- /service/sequencer/sequencer.go: -------------------------------------------------------------------------------- 1 | package sequencer 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/crypto" 9 | "github.com/pkg/errors" 10 | 11 | "github.com/iotexproject/w3bstream/datasource" 12 | "github.com/iotexproject/w3bstream/monitor" 13 | "github.com/iotexproject/w3bstream/service/sequencer/api" 14 | "github.com/iotexproject/w3bstream/service/sequencer/config" 15 | "github.com/iotexproject/w3bstream/service/sequencer/db" 16 | "github.com/iotexproject/w3bstream/task/assigner" 17 | ) 18 | 19 | type Sequencer struct { 20 | cfg *config.Config 21 | db *db.DB 22 | privateKey *ecdsa.PrivateKey 23 | } 24 | 25 | func NewSequencer(cfg *config.Config, db *db.DB, prv *ecdsa.PrivateKey) *Sequencer { 26 | return &Sequencer{ 27 | cfg: cfg, 28 | db: db, 29 | privateKey: prv, 30 | } 31 | } 32 | 33 | func (s *Sequencer) Start() error { 34 | if err := monitor.Run( 35 | &monitor.Handler{ 36 | ScannedBlockNumber: s.db.ScannedBlockNumber, 37 | UpsertScannedBlockNumber: s.db.UpsertScannedBlockNumber, 38 | UpsertProver: s.db.UpsertProver, 39 | SettleTask: s.db.DeleteTask, 40 | }, 41 | &monitor.ContractAddr{ 42 | Prover: common.HexToAddress(s.cfg.ProverContractAddr), 43 | Minter: common.HexToAddress(s.cfg.MinterContractAddr), 44 | TaskManager: common.HexToAddress(s.cfg.TaskManagerContractAddr), 45 | }, 46 | s.cfg.BeginningBlockNumber, 47 | s.cfg.ChainEndpoint, 48 | ); err != nil { 49 | return errors.Wrap(err, "failed to start monitor") 50 | } 51 | 52 | datasource, err := datasource.NewClickhouse(s.cfg.DatasourceDSN) 53 | if err != nil { 54 | return errors.Wrap(err, "failed to new datasource") 55 | } 56 | 57 | err = assigner.Run(s.db, s.privateKey, s.cfg.ChainEndpoint, datasource.Retrieve, common.HexToAddress(s.cfg.MinterContractAddr), s.cfg.TaskProcessingBandwidth) 58 | 59 | go func() { 60 | if err := api.Run(s.cfg.ServiceEndpoint, s.db, datasource.Retrieve); err != nil { 61 | log.Fatal(err) 62 | } 63 | }() 64 | 65 | return errors.Wrap(err, "failed to run task assigner") 66 | } 67 | 68 | func (s *Sequencer) Stop() error { 69 | return nil 70 | } 71 | 72 | func (s *Sequencer) Address() common.Address { 73 | if s.privateKey == nil { 74 | log.Fatal("private key is nil") 75 | } 76 | return crypto.PubkeyToAddress(s.privateKey.PublicKey) 77 | } 78 | -------------------------------------------------------------------------------- /smartcontracts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends" : [ 4 | "eslint:recommended", 5 | "prettier" 6 | ], 7 | "env": { 8 | "es2022": true, 9 | "browser": true, 10 | "node": true, 11 | "mocha": true 12 | }, 13 | "globals" : { 14 | "artifacts": "readonly", 15 | "contract": "readonly", 16 | "web3": "readonly", 17 | "extendEnvironment": "readonly", 18 | "expect": "readonly" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /smartcontracts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | 4 | # Hardhat files 5 | /cache 6 | /artifacts 7 | 8 | # TypeChain files 9 | /typechain 10 | /typechain-types 11 | 12 | # solidity-coverage files 13 | /coverage 14 | /coverage.json 15 | -------------------------------------------------------------------------------- /smartcontracts/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "arrowParens": "avoid", 6 | "overrides": [ 7 | { 8 | "files": "*.sol", 9 | "options": { 10 | "singleQuote": false 11 | } 12 | } 13 | ], 14 | "plugins": ["prettier-plugin-solidity"] 15 | } 16 | -------------------------------------------------------------------------------- /smartcontracts/contracts/GeodnetDapp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "./interfaces/IDapp.sol"; 5 | 6 | interface IMarshalDAOTicker { 7 | function tick(address _device) external; 8 | } 9 | 10 | contract GeodnetDapp is IDapp { 11 | address public verifier; 12 | address public ticker; 13 | 14 | constructor(address _verifier, address _ticker) { 15 | verifier = _verifier; 16 | ticker = _ticker; 17 | } 18 | 19 | function process( 20 | address _prover, 21 | uint256 _projectId, 22 | bytes32[] calldata _taskIds, 23 | bytes calldata _data 24 | ) external override { 25 | require(_data.length == 23 * 32, "Invalid data length"); 26 | 27 | // Prepare function selector 28 | bytes4 selector = bytes4(keccak256("verifyProof(uint256[8],uint256[2],uint256[2],uint256[11])")); 29 | 30 | // Call verifier contract 31 | (bool success, ) = verifier.staticcall(abi.encodePacked(selector, _data)); 32 | require(success, "Verifier call failed"); 33 | 34 | bytes32[] memory data = bytesToBytes32Array(_data); 35 | uint256 j = 0; 36 | for (uint256 i = 12; i < 22; i++) { 37 | bool isMoved = isBitSet(data[22], j); 38 | j++; 39 | if (isMoved) { 40 | address deviceAddr = address(uint160(uint256(data[i]))); 41 | IMarshalDAOTicker(ticker).tick(deviceAddr); 42 | } 43 | } 44 | } 45 | 46 | function bytesToBytes32Array(bytes memory data) public pure returns (bytes32[] memory) { 47 | uint256 dataNb = data.length / 32; 48 | bytes32[] memory dataList = new bytes32[](dataNb); 49 | uint256 index = 0; 50 | for (uint256 i = 32; i <= data.length; i = i + 32) { 51 | bytes32 temp; 52 | assembly { 53 | temp := mload(add(data, i)) 54 | } 55 | dataList[index] = temp; 56 | index++; 57 | } 58 | return (dataList); 59 | } 60 | 61 | function isBitSet(bytes32 bitmap, uint256 index) public pure returns (bool) { 62 | require(index < 256, "Index out of bounds"); 63 | return (bitmap & bytes32(1 << index)) != 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /smartcontracts/contracts/ProjectDevice.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | 6 | interface IioID { 7 | function ownerOf(uint256 tokenId) external view returns (address owner); 8 | } 9 | 10 | interface IioIDRegistry { 11 | function ioID() external view returns (address); 12 | function deviceTokenId(address device) external view returns (uint256); 13 | } 14 | 15 | interface IW3bstreamProject { 16 | function ownerOf(uint256 _projectId) external view returns (address); 17 | function isValidProject(uint256 _projectId) external view returns (bool); 18 | } 19 | 20 | contract ProjectDevice is Initializable { 21 | event Approve(uint256 projectId, address indexed device); 22 | event Unapprove(uint256 projectId, address indexed device); 23 | 24 | IioIDRegistry public ioIDRegistry; 25 | IW3bstreamProject public w3bstreamProject; 26 | 27 | mapping(uint256 => mapping(address => bool)) devices; 28 | 29 | function initialize(address _ioIDRegistry, address _w3bstreamProject) external initializer { 30 | ioIDRegistry = IioIDRegistry(_ioIDRegistry); 31 | w3bstreamProject = IW3bstreamProject(_w3bstreamProject); 32 | } 33 | 34 | function approve(uint256 _projectId, address[] calldata _devices) external { 35 | require(w3bstreamProject.isValidProject(_projectId), "invalid project"); 36 | require(w3bstreamProject.ownerOf(_projectId) == msg.sender, "not project owner"); 37 | 38 | for (uint i = 0; i < _devices.length; i++) { 39 | address _device = _devices[i]; 40 | require(!devices[_projectId][_device], "already approved"); 41 | 42 | uint256 _tokenId = ioIDRegistry.deviceTokenId(_device); 43 | IioID ioID = IioID(ioIDRegistry.ioID()); 44 | require(ioID.ownerOf(_tokenId) != address(0), "invalid device"); 45 | 46 | devices[_projectId][_device] = true; 47 | emit Approve(_projectId, _device); 48 | } 49 | } 50 | 51 | function unapprove(uint256 _projectId, address[] calldata _devices) external { 52 | require(w3bstreamProject.isValidProject(_projectId), "invalid project"); 53 | require(w3bstreamProject.ownerOf(_projectId) == msg.sender, "not project owner"); 54 | 55 | for (uint i = 0; i < _devices.length; i++) { 56 | address _device = _devices[i]; 57 | require(devices[_projectId][_device], "not approve"); 58 | 59 | devices[_projectId][_device] = false; 60 | emit Unapprove(_projectId, _device); 61 | } 62 | } 63 | 64 | function approved(uint256 _projectId, address _device) external view returns (bool) { 65 | return devices[_projectId][_device]; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /smartcontracts/contracts/ProjectRegistrar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | 6 | interface IProjectStore { 7 | function bind(uint256 _projectId) external; 8 | } 9 | 10 | contract ProjectRegistrar is OwnableUpgradeable { 11 | event ProjectRegistered(uint256 indexed projectId); 12 | event RegistrationFeeSet(uint256 fee); 13 | event FeeWithdrawn(address indexed account, uint256 amount); 14 | 15 | uint256 public registrationFee; 16 | IProjectStore public projectStore; 17 | 18 | function initialize(address _projectStore) public initializer { 19 | __Ownable_init(); 20 | projectStore = IProjectStore(_projectStore); 21 | } 22 | 23 | function setRegistrationFee(uint256 _fee) public onlyOwner { 24 | registrationFee = _fee; 25 | emit RegistrationFeeSet(_fee); 26 | } 27 | 28 | function register(uint256 _projectId) external payable { 29 | require(msg.value >= registrationFee, "insufficient fee"); 30 | projectStore.bind(_projectId); 31 | } 32 | 33 | function withdrawFee(address payable _account, uint256 _amount) external onlyOwner { 34 | (bool success, ) = _account.call{value: _amount}(""); 35 | require(success, "withdraw fee fail"); 36 | 37 | emit FeeWithdrawn(_account, _amount); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /smartcontracts/contracts/W3bstreamDebits.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | contract W3bstreamDebits is OwnableUpgradeable { 8 | event OperatorSet(address indexed operator); 9 | event Deposited(address indexed token, address indexed owner, uint256 amount); 10 | event Withheld(address indexed token, address indexed owner, uint256 amount); 11 | event Distributed(address indexed token, address indexed owner, address[] recipients, uint256[] amounts); 12 | event Redeemed(address indexed token, address indexed owner, uint256 amount); 13 | event Withdrawn(address indexed token, address indexed owner, uint256 amount); 14 | 15 | address public operator; 16 | 17 | mapping(address => mapping(address => uint256)) balances; 18 | mapping(address => mapping(address => uint256)) withholdings; 19 | 20 | modifier onlyOperator() { 21 | require(msg.sender == operator, "not debits operator"); 22 | _; 23 | } 24 | 25 | function initialize() public initializer { 26 | __Ownable_init(); 27 | } 28 | 29 | function setOperator(address _operator) external onlyOwner { 30 | operator = _operator; 31 | emit OperatorSet(_operator); 32 | } 33 | 34 | function deposit(address token, uint256 amount) external { 35 | require(token != address(0), "invalid token"); 36 | bool success = IERC20(token).transferFrom(msg.sender, address(this), amount); 37 | require(success, "transfer failed"); 38 | balances[token][msg.sender] += amount; 39 | emit Deposited(token, msg.sender, amount); 40 | } 41 | 42 | function withhold(address token, address owner, uint256 amount) external onlyOperator { 43 | require(token != address(0), "reward token not set"); 44 | require(balances[token][owner] >= amount, "insufficient balance"); 45 | withholdings[token][owner] += amount; 46 | balances[token][owner] -= amount; 47 | emit Withheld(token, owner, amount); 48 | } 49 | 50 | function distribute( 51 | address token, 52 | address _owner, 53 | address[] calldata _recipients, 54 | uint256[] calldata _amounts 55 | ) external onlyOperator { 56 | require(token != address(0), "reward token not set"); 57 | require(_recipients.length == _amounts.length, "length mismatch"); 58 | for (uint256 i = 0; i < _recipients.length; i++) { 59 | withholdings[token][_owner] -= _amounts[i]; // overflow protected 60 | balances[token][_recipients[i]] += _amounts[i]; 61 | } 62 | emit Distributed(token, _owner, _recipients, _amounts); 63 | } 64 | 65 | function redeem(address token, address _owner, uint256 _amount) external onlyOperator { 66 | require(token != address(0), "invalid token"); 67 | require(withholdings[token][_owner] >= _amount, "insufficient balance"); 68 | withholdings[token][_owner] -= _amount; 69 | balances[token][_owner] += _amount; 70 | emit Redeemed(token, _owner, _amount); 71 | } 72 | 73 | function withdraw(address _token, uint256 _amount) external { 74 | address sender = msg.sender; 75 | require(_token != address(0), "zero address"); 76 | require(balances[_token][sender] >= _amount, "insufficient balance"); 77 | balances[_token][sender] -= _amount; 78 | bool success = IERC20(_token).transfer(sender, _amount); 79 | require(success, "transfer failed"); 80 | emit Withdrawn(_token, sender, _amount); 81 | } 82 | 83 | function balanceOf(address token, address owner) external view returns (uint256) { 84 | return balances[token][owner]; 85 | } 86 | 87 | function withholdingOf(address token, address owner) external view returns (uint256) { 88 | return withholdings[token][owner]; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /smartcontracts/contracts/W3bstreamMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import {ITaskManager, TaskAssignment} from "./interfaces/ITaskManager.sol"; 6 | 7 | interface IRewardDistributor { 8 | function distribute(address recipient, uint256 amount) external; 9 | } 10 | 11 | struct Sequencer { 12 | address addr; 13 | address operator; 14 | address beneficiary; 15 | } 16 | 17 | contract W3bstreamMinter is OwnableUpgradeable { 18 | event RewardSet(uint256 reward); 19 | event TaskAllowanceSet(uint256 allowance); 20 | event TargetDurationSet(uint256 duration); 21 | 22 | ITaskManager public taskManager; 23 | IRewardDistributor public distributor; 24 | 25 | uint256 public reward; 26 | uint256 public taskAllowance; 27 | 28 | function initialize(ITaskManager _taskManager, IRewardDistributor _distributor) public initializer { 29 | __Ownable_init(); 30 | taskManager = _taskManager; 31 | distributor = _distributor; 32 | _setReward(1000000000000000000); 33 | _setTaskAllowance(720); 34 | } 35 | 36 | function mint(Sequencer calldata coinbase, TaskAssignment[] calldata assignments) public { 37 | require(coinbase.operator == msg.sender, "invalid coinbase operator"); 38 | taskManager.assign(assignments, coinbase.beneficiary, block.number + taskAllowance); 39 | distributor.distribute(coinbase.beneficiary, reward); 40 | } 41 | 42 | function setReward(uint256 _reward) public onlyOwner { 43 | _setReward(_reward); 44 | } 45 | 46 | function _setReward(uint256 _reward) internal { 47 | reward = _reward; 48 | emit RewardSet(reward); 49 | } 50 | 51 | function setTaskAllowance(uint256 allowance) public onlyOwner { 52 | _setTaskAllowance(allowance); 53 | } 54 | 55 | function _setTaskAllowance(uint256 allowance) internal { 56 | taskAllowance = allowance; 57 | emit TaskAllowanceSet(allowance); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /smartcontracts/contracts/W3bstreamProjectReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | interface IProject { 8 | function ownerOf(uint256 _projectId) external view returns (address); 9 | 10 | function isPaused(uint256 _projectId) external view returns (bool); 11 | } 12 | 13 | contract W3bstreamProjectReward is OwnableUpgradeable { 14 | event OperatorSet(address indexed operator); 15 | event RewardTokenSet(uint256 indexed id, address indexed rewardToken); 16 | event RewardAmountSet(address indexed owner, uint256 indexed id, uint256 amount); 17 | 18 | address public operator; 19 | address public project; 20 | 21 | mapping(uint256 => address) _rewardTokens; 22 | mapping(address => mapping(uint256 => uint256)) _rewardAmounts; 23 | 24 | modifier onlyOperator() { 25 | require(msg.sender == operator, "not project reward operator"); 26 | _; 27 | } 28 | 29 | function initialize(address _project) public initializer { 30 | __Ownable_init(); 31 | project = _project; 32 | } 33 | 34 | function rewardToken(uint256 _id) external view returns (address) { 35 | return _rewardTokens[_id]; 36 | } 37 | 38 | function rewardAmount(address owner, uint256 id) external view returns (uint256) { 39 | return _rewardAmounts[owner][id]; 40 | } 41 | 42 | function isPaused(uint256 _projectId) external view returns (bool) { 43 | return IProject(project).isPaused(_projectId); 44 | } 45 | 46 | function setRewardToken(uint256 _id, address _rewardToken) external { 47 | require(IProject(project).ownerOf(_id) == msg.sender, "invalid project"); 48 | require(_rewardToken != address(0), "zero address"); 49 | require(_rewardTokens[_id] == address(0), "already set"); 50 | _rewardTokens[_id] = _rewardToken; 51 | emit RewardTokenSet(_id, _rewardToken); 52 | } 53 | 54 | function setReward(uint256 _id, uint256 _amount) external { 55 | address sender = msg.sender; 56 | _rewardAmounts[sender][_id] = _amount; 57 | emit RewardAmountSet(sender, _id, _amount); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /smartcontracts/contracts/W3bstreamProver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | 6 | contract W3bstreamProver is OwnableUpgradeable { 7 | event BeneficiarySet(address indexed prover, address indexed beneficiary); 8 | event VMTypeAdded(address indexed prover, uint256 typ); 9 | event VMTypeDeleted(address indexed prover, uint256 typ); 10 | event ProverPaused(address indexed prover); 11 | event ProverResumed(address indexed prover); 12 | event RebateRatioSet(address indexed prover, uint16 ratio); 13 | 14 | mapping(address => mapping(uint256 => bool)) vmTypes; 15 | mapping(address => uint16) rebateRatios; 16 | mapping(address => address) beneficiaries; 17 | mapping(address => bool) paused; 18 | 19 | function initialize() public initializer { 20 | __Ownable_init(); 21 | } 22 | 23 | function isVMTypeSupported(address _prover, uint256 _type) external view returns (bool) { 24 | return vmTypes[_prover][_type]; 25 | } 26 | 27 | function beneficiary(address _prover) external view returns (address) { 28 | return beneficiaries[_prover]; 29 | } 30 | 31 | function isPaused(address _prover) external view returns (bool) { 32 | return paused[_prover]; 33 | } 34 | 35 | function rebateRatio(address _prover) external view returns (uint16) { 36 | return rebateRatios[_prover]; 37 | } 38 | 39 | function register() external { 40 | address sender = msg.sender; 41 | require(beneficiaries[sender] == address(0), "already registered"); 42 | beneficiaries[sender] = sender; 43 | emit BeneficiarySet(sender, sender); 44 | } 45 | 46 | function setRebateRatio(uint16 _ratio) external { 47 | address sender = msg.sender; 48 | rebateRatios[sender] = _ratio; 49 | emit RebateRatioSet(sender, _ratio); 50 | } 51 | 52 | function addVMType(uint256 _type) external { 53 | address sender = msg.sender; 54 | vmTypes[sender][_type] = true; 55 | emit VMTypeAdded(sender, _type); 56 | } 57 | 58 | function delVMType(uint256 _type) external { 59 | address sender = msg.sender; 60 | vmTypes[sender][_type] = false; 61 | emit VMTypeDeleted(sender, _type); 62 | } 63 | 64 | function changeBeneficiary(address _beneficiary) external { 65 | address sender = msg.sender; 66 | require(_beneficiary != address(0), "zero address"); 67 | beneficiaries[sender] = _beneficiary; 68 | emit BeneficiarySet(sender, _beneficiary); 69 | } 70 | 71 | function pause() external { 72 | address sender = msg.sender; 73 | require(!paused[sender], "already paused"); 74 | 75 | paused[sender] = true; 76 | emit ProverPaused(sender); 77 | } 78 | 79 | function resume() external { 80 | address sender = msg.sender; 81 | require(paused[sender], "already actived"); 82 | 83 | paused[sender] = false; 84 | emit ProverResumed(sender); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /smartcontracts/contracts/W3bstreamRewardDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | 6 | contract W3bstreamRewardDistributor is OwnableUpgradeable { 7 | event Distributed(address indexed recipient, uint256 amount); 8 | event Withdrawn(uint256 amount); 9 | event OperatorSet(address indexed operator); 10 | event Topup(uint256 amount); 11 | address public operator; 12 | 13 | modifier onlyOperator() { 14 | require(msg.sender == operator, "not reward distributor operator"); 15 | _; 16 | } 17 | 18 | receive() external payable { 19 | emit Topup(msg.value); 20 | } 21 | 22 | function initialize() public initializer { 23 | __Ownable_init(); 24 | } 25 | 26 | function setOperator(address _operator) public onlyOwner { 27 | operator = _operator; 28 | emit OperatorSet(_operator); 29 | } 30 | 31 | function distribute(address recipient, uint256 amount) public onlyOperator { 32 | if (amount == 0) { 33 | return; 34 | } 35 | if (amount > address(this).balance) { 36 | revert("insufficient balance"); 37 | } 38 | (bool success, ) = recipient.call{value: amount}(""); 39 | require(success, "transfer failed"); 40 | emit Distributed(recipient, amount); 41 | } 42 | 43 | function withdraw(uint256 amount) public onlyOwner { 44 | require(amount <= address(this).balance, "insufficient balance"); 45 | (bool success, ) = msg.sender.call{value: amount}(""); 46 | require(success, "transfer failed"); 47 | emit Withdrawn(amount); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /smartcontracts/contracts/W3bstreamRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "./interfaces/IRouter.sol"; 5 | import "./interfaces/IDapp.sol"; 6 | import "./interfaces/IIoIDProxy.sol"; 7 | 8 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 9 | import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 10 | import {ITaskManager} from "./interfaces/ITaskManager.sol"; 11 | 12 | interface IProjectStore { 13 | function isPaused(uint256 _projectId) external view returns (bool); 14 | } 15 | 16 | interface IProverStore { 17 | function isPaused(address prover) external view returns (bool); 18 | } 19 | 20 | contract W3bstreamRouter is IRouter, Initializable { 21 | ITaskManager public taskManager; 22 | IProverStore public proverStore; 23 | address public projectStore; 24 | 25 | mapping(uint256 => address) public override dapp; 26 | 27 | modifier onlyProjectOwner(uint256 _projectId) { 28 | address projectOwner = IERC721(projectStore).ownerOf(_projectId); 29 | require(projectOwner == msg.sender || IIoIDProxyOwner(projectOwner).owner() == msg.sender, "not project owner"); 30 | _; 31 | } 32 | 33 | function initialize( 34 | ITaskManager _taskManager, 35 | IProverStore _proverStore, 36 | address _projectStore 37 | ) public initializer { 38 | taskManager = _taskManager; 39 | projectStore = _projectStore; 40 | proverStore = _proverStore; 41 | } 42 | 43 | function route( 44 | address _prover, 45 | uint256 _projectId, 46 | bytes32[] calldata _taskIds, 47 | bytes calldata _data 48 | ) external override { 49 | address _dapp = dapp[_projectId]; 50 | require(_dapp != address(0), "no dapp"); 51 | require(!proverStore.isPaused(_prover), "prover paused"); 52 | require(!IProjectStore(projectStore).isPaused(_projectId), "project paused"); 53 | 54 | IDapp(_dapp).process(_prover, _projectId, _taskIds, _data); 55 | for (uint256 i = 0; i < _taskIds.length; i++) { 56 | taskManager.settle(_projectId, _taskIds[i], _prover); 57 | } 58 | } 59 | 60 | function bindDapp(uint256 _projectId, address _dapp) external override onlyProjectOwner(_projectId) { 61 | dapp[_projectId] = _dapp; 62 | emit DappBound(_projectId, msg.sender, _dapp); 63 | } 64 | 65 | function unbindDapp(uint256 _projectId) external override onlyProjectOwner(_projectId) { 66 | delete dapp[_projectId]; 67 | emit DappUnbound(_projectId, msg.sender); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /smartcontracts/contracts/W3bstreamVMType.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; 6 | 7 | contract W3bstreamVMType is OwnableUpgradeable, ERC721Upgradeable { 8 | event VMTypeSet(uint256 indexed id); 9 | event VMTypePaused(uint256 indexed id); 10 | event VMTypeResumed(uint256 indexed id); 11 | 12 | uint256 nextTypeId; 13 | 14 | mapping(uint256 => string) _types; 15 | mapping(uint256 => bool) _paused; 16 | 17 | modifier onlyVMTypeOwner(uint256 _id) { 18 | require(ownerOf(_id) == msg.sender, "not owner"); 19 | _; 20 | } 21 | 22 | function initialize(string memory _name, string memory _symbol) public initializer { 23 | __Ownable_init(); 24 | __ERC721_init(_name, _symbol); 25 | } 26 | 27 | function count() external view returns (uint256) { 28 | return nextTypeId; 29 | } 30 | 31 | function vmTypeName(uint256 _id) external view returns (string memory) { 32 | _requireMinted(_id); 33 | return _types[_id]; 34 | } 35 | 36 | function isPaused(uint256 _id) external view returns (bool) { 37 | _requireMinted(_id); 38 | return _paused[_id]; 39 | } 40 | 41 | function mint(string memory _name) external returns (uint256 id_) { 42 | id_ = ++nextTypeId; 43 | _mint(msg.sender, id_); 44 | 45 | _types[id_] = _name; 46 | _paused[id_] = false; 47 | emit VMTypeSet(id_); 48 | } 49 | 50 | function pause(uint256 _id) external onlyVMTypeOwner(_id) { 51 | require(!_paused[_id], "already paused"); 52 | 53 | _paused[_id] = true; 54 | emit VMTypePaused(_id); 55 | } 56 | 57 | function resume(uint256 _id) external onlyVMTypeOwner(_id) { 58 | require(_paused[_id], "already actived"); 59 | 60 | _paused[_id] = false; 61 | emit VMTypeResumed(_id); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /smartcontracts/contracts/interfaces/IDapp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface IDapp { 5 | function process(address _prover, uint256 _projectId, bytes32[] calldata _taskIds, bytes calldata _data) external; 6 | } 7 | -------------------------------------------------------------------------------- /smartcontracts/contracts/interfaces/IIoIDProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface IIoIDProxyOwner { 5 | function owner() external view returns (address); 6 | } 7 | -------------------------------------------------------------------------------- /smartcontracts/contracts/interfaces/IRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface IRouter { 5 | event DappBound(uint256 indexed projectId, address indexed operator, address dapp); 6 | event DappUnbound(uint256 indexed projectId, address indexed operator); 7 | 8 | function dapp(uint256 _projectId) external view returns (address); 9 | 10 | function bindDapp(uint256 _projectId, address _dapp) external; 11 | 12 | function unbindDapp(uint256 _projectId) external; 13 | 14 | function route(address _prover, uint256 _projectId, bytes32[] calldata _taskIds, bytes calldata _data) external; 15 | } 16 | -------------------------------------------------------------------------------- /smartcontracts/contracts/interfaces/ISlasher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface ISlasher { 5 | function slash(address prover, uint256 amount) external; 6 | } 7 | -------------------------------------------------------------------------------- /smartcontracts/contracts/interfaces/IStakingHub.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface IStakingHub { 5 | // event Stake(uint256 indexed proverId, uint256 amount); 6 | // event Unstake(uint256 indexed proverId, uint256 amount); 7 | // event Withdrawn(uint256 indexed proverId, address indexed account, uint256 amount); 8 | // event Grant(uint256 indexed proverId, uint256 amount); 9 | // event CoordinatorSet(address indexed coordinator); 10 | // event ProverStoreSet(address indexed prover); 11 | // event SlasherSet(address indexed slasher); 12 | 13 | // function stake(uint256 _proverId) external payable; 14 | // function unstake(uint256 _proverId, uint256 _amount) external; 15 | // function withdraw(uint256 _proverId, address _to) external; 16 | function stakedAmount(address account) external view returns (uint256); 17 | } 18 | -------------------------------------------------------------------------------- /smartcontracts/contracts/interfaces/ITaskManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface ITaskManager { 5 | function assign(TaskAssignment[] calldata assignments, address sequencer, uint256 deadline) external; 6 | 7 | function settle(uint256 projectId, bytes32 taskId, address prover) external; 8 | } 9 | 10 | struct TaskAssignment { 11 | uint256 projectId; 12 | bytes32 taskId; 13 | bytes32 hash; 14 | bytes signature; 15 | address prover; 16 | } 17 | -------------------------------------------------------------------------------- /smartcontracts/contracts/test/MockDapp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract MockDapp { 5 | error CustomError(); 6 | 7 | uint8 public errorType; 8 | 9 | function setErrorType(uint8 _errorType) external { 10 | errorType = _errorType; 11 | } 12 | 13 | function process( 14 | address _prover, 15 | uint256 _projectId, 16 | bytes32[] calldata _taskIds, 17 | bytes calldata _data 18 | ) external view { 19 | if (errorType == 1) { 20 | require(false, "Normal Error"); 21 | } else if (errorType == 2) { 22 | revert CustomError(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /smartcontracts/contracts/test/MockDappLiveness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract MockDappLiveness { 5 | error CustomError(); 6 | 7 | uint8 public errorType; 8 | address public verifier; 9 | 10 | constructor(address _verifier) { 11 | verifier = _verifier; 12 | } 13 | 14 | function setErrorType(uint8 _errorType) external { 15 | errorType = _errorType; 16 | } 17 | 18 | function process( 19 | uint256 _projectId, 20 | bytes32 _taskId, 21 | address _prover, 22 | address _deviceId, 23 | bytes calldata _data 24 | ) external view { 25 | if (errorType == 1) { 26 | require(false, "Normal Error"); 27 | } else if (errorType == 2) { 28 | revert CustomError(); 29 | } 30 | 31 | // Validate data length (78 uint256 values = 78 * 32 bytes) 32 | require(_data.length == 78 * 32, "Invalid data length"); 33 | 34 | // Prepare function selector 35 | bytes4 selector = bytes4(keccak256("verifyProof(uint256[8],uint256[2],uint256[2],uint256[66])")); 36 | 37 | // Call verifier contract 38 | (bool success, ) = verifier.staticcall(abi.encodePacked(selector, _data)); 39 | require(success, "Verifier call failed"); 40 | 41 | // TODO: cross-validate the public inputs which are the last 66 uint256 values in the _data 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /smartcontracts/contracts/test/MockDappMovementBatch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract MockDappMovementBatch { 5 | error CustomError(); 6 | 7 | uint8 public errorType; 8 | address public verifier; 9 | mapping(address => uint64) public deviceTick; 10 | 11 | constructor(address _verifier) { 12 | verifier = _verifier; 13 | } 14 | 15 | function setErrorType(uint8 _errorType) external { 16 | errorType = _errorType; 17 | } 18 | 19 | function process(address _prover, uint256 _projectId, bytes32[] calldata _taskIds, bytes calldata _data) external { 20 | require(_data.length == 23 * 32, "Invalid data length"); 21 | 22 | // Prepare function selector 23 | bytes4 selector = bytes4(keccak256("verifyProof(uint256[8],uint256[2],uint256[2],uint256[11])")); 24 | 25 | // Call verifier contract 26 | (bool success, ) = verifier.staticcall(abi.encodePacked(selector, _data)); 27 | require(success, "Verifier call failed"); 28 | 29 | bytes32[] memory data = bytesToBytes32Array(_data); 30 | uint256 j = 0; 31 | for (uint256 i = 12; i < 22; i++) { 32 | bool isMoved = isBitSet(data[22], j); 33 | j++; 34 | if (isMoved) { 35 | address deviceAddr = address(uint160(uint256(data[i]))); 36 | deviceTick[deviceAddr]++; 37 | } 38 | } 39 | } 40 | 41 | function bytesToBytes32Array(bytes memory data) public pure returns (bytes32[] memory) { 42 | uint256 dataNb = data.length / 32; 43 | bytes32[] memory dataList = new bytes32[](dataNb); 44 | uint256 index = 0; 45 | for (uint256 i = 32; i <= data.length; i = i + 32) { 46 | bytes32 temp; 47 | assembly { 48 | temp := mload(add(data, i)) 49 | } 50 | dataList[index] = temp; 51 | index++; 52 | } 53 | return (dataList); 54 | } 55 | 56 | function isBitSet(bytes32 bitmap, uint256 index) public pure returns (bool) { 57 | require(index < 256, "Index out of bounds"); 58 | return (bitmap & bytes32(1 << index)) != 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /smartcontracts/contracts/test/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract ERC20Mock is ERC20 { 7 | constructor( 8 | string memory name, 9 | string memory symbol, 10 | address initialAccount, 11 | uint256 initialBalance 12 | ) ERC20(name, symbol) { 13 | _mint(initialAccount, initialBalance); 14 | } 15 | } -------------------------------------------------------------------------------- /smartcontracts/contracts/test/MockIoID.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | 6 | contract MockIoID is ERC721 { 7 | event CreateIoID(address indexed owner, uint256 id, address wallet, string did); 8 | 9 | uint256 _Id; 10 | mapping(address => uint256) public deviceProject; 11 | string public constant METHOD = "did:io:"; 12 | 13 | constructor() ERC721("Mock IoID", "MIoID") {} 14 | 15 | function register(uint256 _projectId, address _device, string memory _deviceDID) external returns (uint256 id_) { 16 | id_ = ++_Id; 17 | 18 | _mint(msg.sender, _Id); 19 | deviceProject[_device] = _projectId; 20 | 21 | emit CreateIoID(msg.sender, id_, msg.sender, _deviceDID); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /smartcontracts/contracts/test/MockIoIDRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | 6 | contract MockIoIDRegistry is OwnableUpgradeable { 7 | mapping(address => uint256) ioids; 8 | 9 | address public ioid; 10 | 11 | function initialize(address _ioid) public initializer { 12 | __Ownable_init(); 13 | ioid = _ioid; 14 | } 15 | 16 | function bind(uint256 _ioid, address _device) external { 17 | ioids[_device] = _ioid; 18 | } 19 | 20 | function deviceTokenId(address _device) external view returns (uint256) { 21 | return ioids[_device]; 22 | } 23 | 24 | function ioID() external view returns (address) { 25 | return ioid; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /smartcontracts/contracts/test/MockProject.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | 6 | contract MockProject is ERC721 { 7 | uint256 _projectId; 8 | 9 | constructor() ERC721("Mock Project", "MPN") {} 10 | 11 | function register() external returns (uint256) { 12 | ++_projectId; 13 | 14 | _mint(msg.sender, _projectId); 15 | return _projectId; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /smartcontracts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Ensure the script stops on errors 4 | set -e 5 | 6 | # Optional: Navigate to the project directory (if needed) 7 | # cd /path/to/your/project 8 | 9 | # Check if Yarn is installed 10 | if ! command -v yarn &> /dev/null; then 11 | echo "Yarn is not installed. Please install it first." 12 | exit 1 13 | fi 14 | 15 | # Check if Hardhat is installed in the project 16 | if ! yarn list --pattern hardhat | grep -q 'hardhat'; then 17 | echo "Hardhat is not installed. Installing Hardhat..." 18 | yarn add --dev hardhat 19 | fi 20 | 21 | # Check if the PRIVATE_KEY environment variable is set 22 | if [[ -z "${PRIVATE_KEY}" ]]; then 23 | echo "Error: PRIVATE_KEY environment variable is not set." 24 | exit 1 25 | fi 26 | 27 | # Default network 28 | NETWORK="mainnet" 29 | 30 | # Parse command line arguments 31 | while [[ $# -gt 0 ]]; do 32 | key="$1" 33 | case $key in 34 | --network) 35 | NETWORK="$2" 36 | shift # past argument 37 | shift # past value 38 | ;; 39 | *) 40 | echo "Missing --network option: $1" 41 | exit 1 42 | ;; 43 | esac 44 | done 45 | 46 | # Run the Hardhat deployment script 47 | echo "Running Hardhat deployment..." 48 | yarn hardhat run scripts/deploy.ts --network $NETWORK 49 | 50 | # Check if the previous command was successful 51 | if [ $? -eq 0 ]; then 52 | echo "Deployment completed successfully." 53 | else 54 | echo "Deployment failed." 55 | exit 1 56 | fi 57 | -------------------------------------------------------------------------------- /smartcontracts/go/mockdappmovementbatch/mock_dapp_movement_batch.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_verifier", 7 | "type": "address" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "inputs": [], 15 | "name": "CustomError", 16 | "type": "error" 17 | }, 18 | { 19 | "inputs": [ 20 | { 21 | "internalType": "bytes", 22 | "name": "data", 23 | "type": "bytes" 24 | } 25 | ], 26 | "name": "bytesToBytes32Array", 27 | "outputs": [ 28 | { 29 | "internalType": "bytes32[]", 30 | "name": "", 31 | "type": "bytes32[]" 32 | } 33 | ], 34 | "stateMutability": "pure", 35 | "type": "function" 36 | }, 37 | { 38 | "inputs": [ 39 | { 40 | "internalType": "address", 41 | "name": "", 42 | "type": "address" 43 | } 44 | ], 45 | "name": "deviceTick", 46 | "outputs": [ 47 | { 48 | "internalType": "uint64", 49 | "name": "", 50 | "type": "uint64" 51 | } 52 | ], 53 | "stateMutability": "view", 54 | "type": "function" 55 | }, 56 | { 57 | "inputs": [], 58 | "name": "errorType", 59 | "outputs": [ 60 | { 61 | "internalType": "uint8", 62 | "name": "", 63 | "type": "uint8" 64 | } 65 | ], 66 | "stateMutability": "view", 67 | "type": "function" 68 | }, 69 | { 70 | "inputs": [ 71 | { 72 | "internalType": "bytes32", 73 | "name": "bitmap", 74 | "type": "bytes32" 75 | }, 76 | { 77 | "internalType": "uint256", 78 | "name": "n", 79 | "type": "uint256" 80 | } 81 | ], 82 | "name": "isBitSet", 83 | "outputs": [ 84 | { 85 | "internalType": "bool", 86 | "name": "", 87 | "type": "bool" 88 | } 89 | ], 90 | "stateMutability": "pure", 91 | "type": "function" 92 | }, 93 | { 94 | "inputs": [ 95 | { 96 | "internalType": "address", 97 | "name": "_prover", 98 | "type": "address" 99 | }, 100 | { 101 | "internalType": "uint256", 102 | "name": "_projectId", 103 | "type": "uint256" 104 | }, 105 | { 106 | "internalType": "bytes32[]", 107 | "name": "_taskIds", 108 | "type": "bytes32[]" 109 | }, 110 | { 111 | "internalType": "bytes", 112 | "name": "_data", 113 | "type": "bytes" 114 | } 115 | ], 116 | "name": "process", 117 | "outputs": [], 118 | "stateMutability": "nonpayable", 119 | "type": "function" 120 | }, 121 | { 122 | "inputs": [ 123 | { 124 | "internalType": "uint8", 125 | "name": "_errorType", 126 | "type": "uint8" 127 | } 128 | ], 129 | "name": "setErrorType", 130 | "outputs": [], 131 | "stateMutability": "nonpayable", 132 | "type": "function" 133 | }, 134 | { 135 | "inputs": [], 136 | "name": "verifier", 137 | "outputs": [ 138 | { 139 | "internalType": "address", 140 | "name": "", 141 | "type": "address" 142 | } 143 | ], 144 | "stateMutability": "view", 145 | "type": "function" 146 | } 147 | ] -------------------------------------------------------------------------------- /smartcontracts/go/mockioidregistry/mock_ioid_registry.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "uint8", 8 | "name": "version", 9 | "type": "uint8" 10 | } 11 | ], 12 | "name": "Initialized", 13 | "type": "event" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": true, 20 | "internalType": "address", 21 | "name": "previousOwner", 22 | "type": "address" 23 | }, 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "newOwner", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "OwnershipTransferred", 32 | "type": "event" 33 | }, 34 | { 35 | "inputs": [ 36 | { 37 | "internalType": "uint256", 38 | "name": "_ioid", 39 | "type": "uint256" 40 | }, 41 | { 42 | "internalType": "address", 43 | "name": "_device", 44 | "type": "address" 45 | } 46 | ], 47 | "name": "bind", 48 | "outputs": [], 49 | "stateMutability": "nonpayable", 50 | "type": "function" 51 | }, 52 | { 53 | "inputs": [ 54 | { 55 | "internalType": "address", 56 | "name": "_device", 57 | "type": "address" 58 | } 59 | ], 60 | "name": "deviceTokenId", 61 | "outputs": [ 62 | { 63 | "internalType": "uint256", 64 | "name": "", 65 | "type": "uint256" 66 | } 67 | ], 68 | "stateMutability": "view", 69 | "type": "function" 70 | }, 71 | { 72 | "inputs": [ 73 | { 74 | "internalType": "address", 75 | "name": "_ioid", 76 | "type": "address" 77 | } 78 | ], 79 | "name": "initialize", 80 | "outputs": [], 81 | "stateMutability": "nonpayable", 82 | "type": "function" 83 | }, 84 | { 85 | "inputs": [], 86 | "name": "ioID", 87 | "outputs": [ 88 | { 89 | "internalType": "address", 90 | "name": "", 91 | "type": "address" 92 | } 93 | ], 94 | "stateMutability": "view", 95 | "type": "function" 96 | }, 97 | { 98 | "inputs": [], 99 | "name": "ioid", 100 | "outputs": [ 101 | { 102 | "internalType": "address", 103 | "name": "", 104 | "type": "address" 105 | } 106 | ], 107 | "stateMutability": "view", 108 | "type": "function" 109 | }, 110 | { 111 | "inputs": [], 112 | "name": "owner", 113 | "outputs": [ 114 | { 115 | "internalType": "address", 116 | "name": "", 117 | "type": "address" 118 | } 119 | ], 120 | "stateMutability": "view", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [], 125 | "name": "renounceOwnership", 126 | "outputs": [], 127 | "stateMutability": "nonpayable", 128 | "type": "function" 129 | }, 130 | { 131 | "inputs": [ 132 | { 133 | "internalType": "address", 134 | "name": "newOwner", 135 | "type": "address" 136 | } 137 | ], 138 | "name": "transferOwnership", 139 | "outputs": [], 140 | "stateMutability": "nonpayable", 141 | "type": "function" 142 | } 143 | ] -------------------------------------------------------------------------------- /smartcontracts/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | import { HardhatUserConfig } from 'hardhat/config'; 3 | import '@nomicfoundation/hardhat-toolbox'; 4 | import '@openzeppelin/hardhat-upgrades'; 5 | 6 | dotenv.config(); 7 | 8 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 9 | const accounts = PRIVATE_KEY !== undefined ? [PRIVATE_KEY] : []; 10 | 11 | const config: HardhatUserConfig = { 12 | networks: { 13 | hardhat: { 14 | allowUnlimitedContractSize: true, 15 | }, 16 | dev: { 17 | url: process.env.URL != undefined ? process.env.URL : 'http://127.0.0.1:8545', 18 | accounts: accounts, 19 | }, 20 | nightly: { 21 | url: 'https://babel-nightly.iotex.io', 22 | accounts: accounts, 23 | }, 24 | mainnet: { 25 | url: 'https://babel-api.mainnet.iotex.io/', 26 | accounts: accounts, 27 | }, 28 | testnet: { 29 | url: 'https://babel-api.testnet.iotex.io/', 30 | accounts: accounts, 31 | }, 32 | }, 33 | solidity: { 34 | compilers: [ 35 | { 36 | version: '0.8.19', 37 | settings: { 38 | viaIR: true, 39 | optimizer: { 40 | enabled: true, 41 | runs: 10000, 42 | }, 43 | metadata: { 44 | bytecodeHash: 'none', 45 | }, 46 | }, 47 | }, 48 | ], 49 | }, 50 | etherscan: { 51 | apiKey: 'YOUR_ETHER', 52 | customChains: [ 53 | { 54 | network: 'mainnet', 55 | chainId: 4689, 56 | urls: { 57 | apiURL: 'https://IoTeXscout.io/api', 58 | browserURL: 'https://IoTeXscan.io', 59 | }, 60 | }, 61 | { 62 | network: 'testnet', 63 | chainId: 4690, 64 | urls: { 65 | apiURL: 'https://testnet.IoTeXscout.io/api', 66 | browserURL: 'https://testnet.IoTeXscan.io', 67 | }, 68 | }, 69 | ], 70 | }, 71 | }; 72 | 73 | export default config; 74 | -------------------------------------------------------------------------------- /smartcontracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sprout-contracts", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "clean": "rimraf ./artifacts ./cache", 7 | "compile": "hardhat compile", 8 | "test": "hardhat test", 9 | "snapshots": "UPDATE_SNAPSHOT=1 hardhat test", 10 | "lint": "npm run lint:js && npm run lint:sol", 11 | "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", 12 | "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint --ignore-path .gitignore .", 13 | "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint --ignore-path .gitignore . --fix", 14 | "lint:sol": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'", 15 | "lint:sol:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --write" 16 | }, 17 | "devDependencies": { 18 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", 19 | "@nomicfoundation/hardhat-ethers": "^3.0.0", 20 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 21 | "@nomicfoundation/hardhat-toolbox": "^4.0.0", 22 | "@nomicfoundation/hardhat-verify": "^2.0.0", 23 | "@openzeppelin/hardhat-upgrades": "^3.2.1", 24 | "@typechain/ethers-v6": "^0.5.0", 25 | "@typechain/hardhat": "^9.1.0", 26 | "@types/chai": "^4.2.0", 27 | "@types/mocha": ">=9.1.0", 28 | "@types/node": ">=16.0.0", 29 | "chai": "^4.2.0", 30 | "dotenv": "^16.4.1", 31 | "eslint": "^8.56.0", 32 | "eslint-config-prettier": "^9.1.0", 33 | "ethers": "^6.4.0", 34 | "hardhat": "^2.19.5", 35 | "hardhat-gas-reporter": "^1.0.8", 36 | "prettier": "^3.2.5", 37 | "prettier-plugin-solidity": "^1.3.1", 38 | "solhint": "^4.1.1", 39 | "solidity-coverage": "^0.8.0", 40 | "ts-node": ">=8.0.0", 41 | "typechain": "^8.3.0", 42 | "typescript": "^5.4.3" 43 | }, 44 | "dependencies": { 45 | "@openzeppelin/contracts": "^4.9.5", 46 | "@openzeppelin/contracts-upgradeable": "^4.9.5" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /smartcontracts/scripts/deploy-device.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from 'hardhat'; 2 | 3 | async function main() { 4 | if (!process.env.IOID_REGISTRY) { 5 | console.log(`Please provide ioID Registry address`); 6 | return; 7 | } 8 | if (!process.env.W3BSTREAM_PROJECT) { 9 | console.log(`Please provide w3bstream project address`); 10 | return; 11 | } 12 | 13 | const ProjectDevice = await ethers.getContractFactory('ProjectDevice'); 14 | const pd = await upgrades.deployProxy(ProjectDevice, [process.env.IOID_REGISTRY, process.env.W3BSTREAM_PROJECT], { 15 | initializer: 'initialize', 16 | }); 17 | await pd.waitForDeployment(); 18 | console.log(`ProjectDevice deployed to ${pd.target}`); 19 | } 20 | 21 | main().catch(err => { 22 | console.error(err); 23 | process.exitCode = 1; 24 | }); 25 | -------------------------------------------------------------------------------- /smartcontracts/scripts/deploy-geod.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from 'hardhat'; 2 | 3 | async function main() { 4 | const GeodnetDapp = await ethers.deployContract('GeodnetDapp', [process.env.MOVEMENT_VERIFIER_ADDR, process.env.MARSHALDAO_TICKER_ADDR]); 5 | await GeodnetDapp.waitForDeployment(); 6 | console.log(`GeodnetDapp deployed to ${GeodnetDapp.target}`); 7 | } 8 | 9 | main().catch(err => { 10 | console.error(err); 11 | process.exitCode = 1; 12 | }); 13 | -------------------------------------------------------------------------------- /smartcontracts/scripts/upgrade.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from 'hardhat'; 2 | 3 | async function main() { 4 | 5 | // if (process.env.W3BSTREAM_TASK_MANAGER) { 6 | // const W3bstreamTaskManager = await ethers.getContractFactory('W3bstreamTaskManager'); 7 | // await upgrades.forceImport(process.env.W3BSTREAM_TASK_MANAGER, W3bstreamTaskManager); 8 | // await upgrades.upgradeProxy(process.env.W3BSTREAM_TASK_MANAGER, W3bstreamTaskManager, {}); 9 | // console.log(`Upgrade W3bstreamTaskManager ${process.env.W3BSTREAM_TASK_MANAGER} successfull!`); 10 | // } 11 | 12 | 13 | 14 | if (process.env.W3BSTREAM_TASK_MANAGER) { 15 | const W3bstreamTaskManager = await ethers.getContractFactory('W3bstreamTaskManager'); 16 | await upgrades.upgradeProxy(process.env.W3BSTREAM_TASK_MANAGER, W3bstreamTaskManager, {}); 17 | console.log(`Upgrade W3bstreamTaskManager ${process.env.W3BSTREAM_TASK_MANAGER} successfull!`); 18 | } 19 | } 20 | 21 | main().catch(err => { 22 | console.error(err); 23 | process.exitCode = 1; 24 | }); 25 | -------------------------------------------------------------------------------- /smartcontracts/solhint.config.js: -------------------------------------------------------------------------------- 1 | const rules = [ 2 | 'avoid-tx-origin', 3 | 'const-name-snakecase', 4 | 'contract-name-camelcase', 5 | 'event-name-camelcase', 6 | 'explicit-types', 7 | 'func-name-mixedcase', 8 | 'func-param-name-mixedcase', 9 | 'imports-on-top', 10 | 'modifier-name-mixedcase', 11 | 'no-console', 12 | 'no-global-import', 13 | 'no-unused-vars', 14 | 'quotes', 15 | 'use-forbidden-name', 16 | 'var-name-mixedcase', 17 | 'visibility-modifier-order', 18 | ]; 19 | 20 | module.exports = { 21 | rules: Object.fromEntries(rules.map(r => [r, 'error'])), 22 | }; 23 | -------------------------------------------------------------------------------- /smartcontracts/test/ProjectRegistrar.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | describe('Project Registrar', function () { 5 | let project; 6 | let projectRegistrar; 7 | let w3bstreamProject; 8 | beforeEach(async function () { 9 | project = await ethers.deployContract('MockProject'); 10 | w3bstreamProject = await ethers.deployContract('W3bstreamProject'); 11 | await w3bstreamProject.initialize(project.target); 12 | projectRegistrar = await ethers.deployContract('ProjectRegistrar'); 13 | await projectRegistrar.initialize(w3bstreamProject.getAddress()); 14 | await projectRegistrar.setRegistrationFee(12345); 15 | }); 16 | it('register', async function () { 17 | const [owner, projectOwner] = await ethers.getSigners(); 18 | await w3bstreamProject.setBinder(projectRegistrar.getAddress()); 19 | expect(await w3bstreamProject.count()).to.equal(0); 20 | 21 | await project.connect(projectOwner).register(); 22 | 23 | await projectRegistrar.connect(projectOwner).register(1, { value: 12345 }); 24 | expect(await w3bstreamProject.count()).to.equal(1); 25 | expect(await w3bstreamProject.ownerOf(1)).to.equal(projectOwner.address); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /smartcontracts/test/W3bstreamMinter.ts: -------------------------------------------------------------------------------- 1 | // import { expect } from 'chai'; 2 | // import { ethers } from 'hardhat'; 3 | 4 | // describe('W3bstream Minter', function () { 5 | // let minter; 6 | // let dao; 7 | // let tm; 8 | // let brd; 9 | // let bhv; 10 | // let scrypt; 11 | // const genesis = "0x0000000000000000000000000000000000000000000000000000000000000000"; 12 | // beforeEach(async function () { 13 | // minter = await ethers.deployContract('W3bstreamBlockMinter'); 14 | // dao = await ethers.deployContract('W3bstreamDAO'); 15 | // tm = await ethers.deployContract('MockTaskManager'); 16 | // brd = await ethers.deployContract('W3bstreamBlockRewardDistributor'); 17 | // scrypt = await ethers.deployContract('MockScrypt'); 18 | // bhv = await ethers.deployContract('W3bstreamBlockHeaderValidator', [scrypt.getAddress()]); 19 | // await dao.initialize(genesis); 20 | // await brd.initialize(); 21 | // await minter.initialize(dao.getAddress(), tm.getAddress(), brd.getAddress(), bhv.getAddress()); 22 | // await dao.transferOwnership(minter.getAddress()); 23 | // await brd.setOperator(minter.getAddress()); 24 | // await bhv.setOperator(minter.getAddress()); 25 | // await minter.setBlockReward(0); 26 | // }); 27 | // it('mint block', async function () { 28 | // const tip = await ethers.provider.getBlock('latest'); 29 | // const [owner, sequencer, prover] = await ethers.getSigners(); 30 | // const coinbase = { 31 | // addr: sequencer.address, 32 | // operator: sequencer.address, 33 | // beneficiary: sequencer.address, 34 | // }; 35 | // await bhv.connect(owner).setAdhocNBits("0x1cffff00"); 36 | // let currentNBits = await bhv.currentNBits(); 37 | // const merkleRoot = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode( 38 | // ["address", "address", "address"], 39 | // [coinbase.addr, coinbase.operator, coinbase.beneficiary] 40 | // )); 41 | // let tipinfo = await dao.tip(); 42 | // expect(tipinfo[0]).to.equal(0); 43 | // expect(tipinfo[1]).to.equal(genesis); 44 | // await scrypt.setHash("0x00000000ffff0000000000000000000000000000000000000000000000000000"); 45 | // let blockinfo = { 46 | // meta: "0x00000000", 47 | // prevhash: genesis, 48 | // merkleRoot: merkleRoot, 49 | // nbits: currentNBits, 50 | // nonce: "0x0000000000000000", 51 | // }; 52 | // const nbits = await bhv.currentNBits(); 53 | // const currentTarget = await bhv.nbitsToTarget(nbits); 54 | // await minter.connect(sequencer).mint( 55 | // blockinfo, 56 | // coinbase, 57 | // [], 58 | // ); 59 | // tipinfo = await dao.tip(); 60 | // expect(tipinfo[0]).to.equal(1); 61 | // await expect(minter.connect(sequencer).mint( 62 | // blockinfo, 63 | // coinbase, 64 | // [], 65 | // )).to.be.revertedWith("invalid prevhash"); 66 | // blockinfo.prevhash = tipinfo[1]; 67 | // blockinfo.nbits = "0x00008000"; 68 | // await expect(minter.connect(sequencer).mint( 69 | // blockinfo, 70 | // coinbase, 71 | // [], 72 | // )).to.be.revertedWith("invalid nbits"); 73 | // currentNBits = await bhv.currentNBits(); 74 | // blockinfo.nbits = currentNBits; 75 | // await minter.connect(sequencer).mint( 76 | // blockinfo, 77 | // coinbase, 78 | // [], 79 | // ); 80 | // tipinfo = await dao.tip(); 81 | // expect(tipinfo[0]).to.equal(2); 82 | // }); 83 | // }); 84 | -------------------------------------------------------------------------------- /smartcontracts/test/W3bstreamProject.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | describe('W3bstream Project', function () { 5 | let project; 6 | let w3bstreamProject; 7 | beforeEach(async function () { 8 | project = await ethers.deployContract('MockProject'); 9 | w3bstreamProject = await ethers.deployContract('W3bstreamProject'); 10 | }); 11 | it('bind project', async function () { 12 | const [owner, binder, projectOwner] = await ethers.getSigners(); 13 | 14 | await project.connect(projectOwner).register(); 15 | 16 | await w3bstreamProject.initialize(project.target); 17 | await w3bstreamProject.setBinder(binder.address); 18 | const projectId = 1; 19 | 20 | await w3bstreamProject.connect(binder).bind(projectId); 21 | // TODO: read project id from event 22 | expect(await w3bstreamProject.ownerOf(projectId)).to.equal(projectOwner.address); 23 | it('update config successfully', async function () { 24 | const uri = 'http://test'; 25 | const hash = '0x0000000011111111222222223333333344444444555555556666666677777777'; 26 | await w3bstreamProject.connect(projectOwner).updateConfig(projectId, uri, hash); 27 | const cfg = await w3bstreamProject.config(projectId); 28 | expect(cfg[0]).to.equal(uri); 29 | expect(cfg[1]).to.equal(hash); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /smartcontracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "encoding/json" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | type Task struct { 15 | ID common.Hash `json:"id"` 16 | Nonce uint64 `json:"nonce"` 17 | ProjectID *big.Int `json:"projectID"` 18 | ProjectVersion string `json:"projectVersion,omitempty"` 19 | DevicePubKey []byte `json:"devicePublicKey"` 20 | Payload []byte `json:"payload"` 21 | Signature []byte `json:"signature,omitempty"` 22 | PayloadHash common.Hash `json:"payloadHash,omitempty"` 23 | TaskHash common.Hash `json:"taskHash,omitempty"` 24 | PrevTask *Task `json:"previousTask,omitempty"` 25 | } 26 | 27 | func (t *Task) Sign(prv *ecdsa.PrivateKey) ([]byte, error) { 28 | h, err := t.Hash() 29 | if err != nil { 30 | return nil, err 31 | } 32 | sig, err := crypto.Sign(h.Bytes(), prv) 33 | if err != nil { 34 | return nil, errors.Wrap(err, "failed to sign") 35 | } 36 | sig[64] += 27 37 | return sig, nil 38 | } 39 | 40 | func (t *Task) VerifySignature(pubKey []byte) error { 41 | h, err := t.Hash() 42 | if err != nil { 43 | return err 44 | } 45 | sigpk, err := crypto.Ecrecover(h.Bytes(), t.Signature) 46 | if err != nil { 47 | return errors.Wrap(err, "failed to recover public key") 48 | } 49 | if !bytes.Equal(sigpk, pubKey) { 50 | return errors.New("task signature unmatched") 51 | } 52 | return nil 53 | } 54 | 55 | func (t *Task) Hash() (common.Hash, error) { 56 | nt := *t 57 | nt.Signature = nil 58 | j, err := json.Marshal(&nt) 59 | if err != nil { 60 | return common.Hash{}, errors.Wrap(err, "failed to marshal task") 61 | } 62 | 63 | return crypto.Keccak256Hash(j), nil 64 | } 65 | -------------------------------------------------------------------------------- /template/conf-output-template/ethereumContract.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ethereumContract", 3 | "ethereum": { 4 | "chainEndpoint": "https://babel-api.testnet.iotex.io", 5 | "contractAddress": "0xa54B215fE14fC7e8462B10b55cfb19ce871a50F4", 6 | "receiverAddress": "0xa54B215fE14fC7e8462B10b55cfb19ce871a50F4", 7 | "contractMethod": "submit", 8 | "contractAbiJSON": "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_projectId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_receiver\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data_snark\",\"type\":\"bytes\"}],\"name\":\"submit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /template/conf-output-template/stdout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stdout" 3 | } 4 | -------------------------------------------------------------------------------- /template/conf-output-template/textile.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "textile", 3 | "textile": { 4 | "vaultID": "qod_poc_vault.data" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /template/contract/Store.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "name": "getJournal", 5 | "outputs": [ 6 | { 7 | "internalType": "bytes", 8 | "name": "", 9 | "type": "bytes" 10 | } 11 | ], 12 | "stateMutability": "view", 13 | "type": "function" 14 | }, 15 | { 16 | "inputs": [], 17 | "name": "getPostStateDigest", 18 | "outputs": [ 19 | { 20 | "internalType": "bytes", 21 | "name": "", 22 | "type": "bytes" 23 | } 24 | ], 25 | "stateMutability": "view", 26 | "type": "function" 27 | }, 28 | { 29 | "inputs": [], 30 | "name": "getProjectId", 31 | "outputs": [ 32 | { 33 | "internalType": "uint256", 34 | "name": "", 35 | "type": "uint256" 36 | } 37 | ], 38 | "stateMutability": "view", 39 | "type": "function" 40 | }, 41 | { 42 | "inputs": [], 43 | "name": "getReceiver", 44 | "outputs": [ 45 | { 46 | "internalType": "address", 47 | "name": "", 48 | "type": "address" 49 | } 50 | ], 51 | "stateMutability": "view", 52 | "type": "function" 53 | }, 54 | { 55 | "inputs": [], 56 | "name": "getSeal", 57 | "outputs": [ 58 | { 59 | "internalType": "bytes", 60 | "name": "", 61 | "type": "bytes" 62 | } 63 | ], 64 | "stateMutability": "view", 65 | "type": "function" 66 | }, 67 | { 68 | "inputs": [ 69 | { 70 | "internalType": "bytes", 71 | "name": "_proof", 72 | "type": "bytes" 73 | } 74 | ], 75 | "name": "setProof", 76 | "outputs": [], 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "inputs": [ 82 | { 83 | "internalType": "uint256", 84 | "name": "_projectId", 85 | "type": "uint256" 86 | }, 87 | { 88 | "internalType": "address", 89 | "name": "_receiver", 90 | "type": "address" 91 | }, 92 | { 93 | "internalType": "bytes", 94 | "name": "_data_snark", 95 | "type": "bytes" 96 | } 97 | ], 98 | "name": "submit", 99 | "outputs": [], 100 | "stateMutability": "nonpayable", 101 | "type": "function" 102 | } 103 | ] -------------------------------------------------------------------------------- /template/contract/Store.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | contract Store { 4 | bytes private proof; 5 | bytes private proof_seal; 6 | bytes private proof_post_state_digest; 7 | bytes private proof_journal; 8 | uint256 private projectId; 9 | address private receiver; 10 | 11 | 12 | function setProof(bytes memory _proof) public { 13 | proof = _proof; 14 | } 15 | 16 | function submit(uint256 _projectId, address _receiver, bytes calldata _data_snark) public { 17 | projectId = _projectId; 18 | receiver = _receiver; 19 | (bytes memory proof_snark_seal, bytes memory proof_snark_post_state_digest, bytes memory proof_snark_journal) = abi.decode(_data_snark, (bytes, bytes, bytes)); 20 | proof_seal = proof_snark_seal; 21 | proof_post_state_digest = proof_snark_post_state_digest; 22 | proof_journal = proof_snark_journal; 23 | } 24 | 25 | function getSeal() public view returns (bytes memory){ 26 | return proof_seal; 27 | } 28 | 29 | function getPostStateDigest() public view returns (bytes memory){ 30 | return proof_post_state_digest; 31 | } 32 | 33 | function getJournal() public view returns (bytes memory){ 34 | return proof_journal; 35 | } 36 | 37 | function getProjectId() public view returns (uint256){ 38 | return projectId; 39 | } 40 | 41 | function getReceiver() public view returns (address ){ 42 | return receiver; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /template/contract/data.go: -------------------------------------------------------------------------------- 1 | package contract 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | ) 9 | 10 | func BuildData(param []byte) ([]byte, error) { 11 | abiJson, err := os.ReadFile("test/contract/Store.abi") 12 | if err != nil { 13 | return nil, err 14 | } 15 | storeABI, err := abi.JSON(bytes.NewReader(abiJson)) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return storeABI.Pack("setProof", param) 20 | } 21 | -------------------------------------------------------------------------------- /template/project_file/gnark_liveness: -------------------------------------------------------------------------------- 1 | { 2 | "defaultVersion": "v1", 3 | "config": [ 4 | { 5 | "version": "v1", 6 | "vmTypeID": 1, 7 | "proofType": "liveness", 8 | "signedKeys": [ 9 | { 10 | "name": "timestamp", 11 | "type": "uint64" 12 | } 13 | ], 14 | "signatureAlgorithm": "ecdsa", 15 | "hashAlgorithm": "sha256", 16 | "metadata": "ipfs://ipfs.mainnet.iotex.io/QmVzFDBXsixjSEdqziCDJe3UnGqUGqmCXXb3FZrySjr8Gh", 17 | "code": "ipfs://ipfs.mainnet.iotex.io/QmfY8NooLr89X8HYnVC8ueZAVjMiBwWi4WmxvBivV1drRo" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /template/project_file/gnark_movement: -------------------------------------------------------------------------------- 1 | {"defaultVersion":"v1","config":[{"version":"v1","vmTypeID":1,"proofType":"movement","signedKeys":[{"name":"timestamp","type":"uint64"},{"name":"latitude","type":"uint64"},{"name":"longitude","type":"uint64"}],"signatureAlgorithm":"ecdsa","hashAlgorithm":"sha256","metadata":"ipfs://ipfs.mainnet.iotex.io/Qmeqey31unmkdsVU2YjqXgVw6uhgVTPzpm9M41yVFjzqRE","code":"ipfs://ipfs.mainnet.iotex.io/QmTwp1B62bEUwZzZhA78x5hj82EHu3KCvGo2hM2wwoJBLd"}]} -------------------------------------------------------------------------------- /template/project_file/movement_batch: -------------------------------------------------------------------------------- 1 | { 2 | "defaultVersion": "v1", 3 | "config": [ 4 | { 5 | "version": "v1", 6 | "vmTypeID": 1, 7 | "proofType": "movement", 8 | "signedKeys": [ 9 | { 10 | "name": "timestamp", 11 | "type": "uint64" 12 | }, 13 | { 14 | "name": "latitude", 15 | "type": "uint64" 16 | }, 17 | { 18 | "name": "longitude", 19 | "type": "uint64" 20 | } 21 | ], 22 | "signatureAlgorithm": "", 23 | "hashAlgorithm": "", 24 | "taskProcessingBatch": 10, 25 | "metadata": "ipfs://ipfs.mainnet.iotex.io/QmTXxKjQEQ8gAzgJjvoxoMB5ocYi16VimHjFwTKf6hEHSm", 26 | "metadataHash": "0xc969f436f4cef28377e3b5ec3ca2457bcaab2795f65c4e1bf3656b57087cd957", 27 | "code": "ipfs://ipfs.mainnet.iotex.io/QmSg3NFgVbHZyUZsHAjrLV7o8CmLKYChNa3CVi4uKKrHeL", 28 | "codeHash": "0xdc3392204a56698891090ea1b729b7a25deba50ccad26ef70ff4b3d51f662ce4" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /template/project_file/zkwasm: -------------------------------------------------------------------------------- 1 | { 2 | "defaultVersion": "0.1", 3 | "versions": [ 4 | { 5 | "vmTypeID": 3, 6 | "output": { 7 | "type": "stdout" 8 | }, 9 | "version": "0.1", 10 | "code": "789c2d8c410e82301444e7f7574860039cc0780e371ec113d0aa5d34da8a95623446af6e8b2c665ef23219e8bb2300d44a05fa28face050588b560e3a7ea9126bdf5431c415994c1dca20d06822503bce274506e651b8c3ef5433c5cec71d98beeef829df46816c9c5ebecb4f5908533ee1a9e02d5860beca8419d80846e46ce3beb7d43f50f7f402616" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /util/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/fatih/color" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | func parseEnvTag(tag string) (key string, require bool) { 14 | if tag == "" || tag == "-" { 15 | return "", false 16 | } 17 | tagKeys := strings.Split(tag, ",") 18 | key = tagKeys[0] 19 | if len(tagKeys) > 1 && tagKeys[1] == "optional" { 20 | return key, false 21 | } 22 | return key, true 23 | } 24 | 25 | func ParseEnv(c any) error { 26 | rv := reflect.ValueOf(c).Elem() 27 | rt := reflect.TypeOf(c).Elem() 28 | 29 | for i := 0; i < rt.NumField(); i++ { 30 | fi := rt.Field(i) 31 | fv := rv.Field(i) 32 | key, require := parseEnvTag(fi.Tag.Get("env")) 33 | if key == "" { 34 | continue 35 | } 36 | viper.MustBindEnv(key) 37 | 38 | v := viper.Get(key) 39 | if require && v == nil && fv.IsZero() { 40 | panic(fmt.Sprintf("env `%s` is require but got empty", key)) 41 | } 42 | if v == nil { 43 | continue 44 | } 45 | 46 | switch fv.Kind() { 47 | case reflect.String: 48 | fv.Set(reflect.ValueOf(viper.GetString(key))) 49 | case reflect.Int: 50 | if fi.Type == reflect.TypeOf(slog.Level(0)) { 51 | level := slog.Level(viper.GetInt(key)) 52 | fv.Set(reflect.ValueOf(level)) 53 | } else { 54 | fv.Set(reflect.ValueOf(viper.GetInt(key))) 55 | } 56 | case reflect.Uint64: 57 | fv.Set(reflect.ValueOf(viper.GetUint64(key))) 58 | } 59 | } 60 | return nil 61 | } 62 | 63 | func Print(c any) { 64 | rt := reflect.TypeOf(c).Elem() 65 | rv := reflect.ValueOf(c).Elem() 66 | 67 | if env, ok := c.(interface{ Env() string }); ok { 68 | fmt.Println(color.CyanString("ENV: %s", env.Env())) 69 | } 70 | 71 | for i := 0; i < rt.NumField(); i++ { 72 | fi := rt.Field(i) 73 | fv := rv.Field(i) 74 | key, _ := parseEnvTag(fi.Tag.Get("env")) 75 | if key == "" { 76 | continue 77 | } 78 | fmt.Printf("%s: %v\n", color.GreenString(key), fv.Interface()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /util/filefetcher/file.go: -------------------------------------------------------------------------------- 1 | package filefetcher 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | 11 | "github.com/iotexproject/w3bstream/util/ipfs" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | type Filedescriptor struct { 16 | Uri string 17 | Hash [32]byte 18 | } 19 | 20 | func (fd *Filedescriptor) FetchFile(skipHash ...bool) ([]byte, error) { 21 | u, err := url.Parse(fd.Uri) 22 | if err != nil { 23 | return nil, errors.Wrapf(err, "failed to parse project file uri %s", fd.Uri) 24 | } 25 | 26 | var data []byte 27 | switch u.Scheme { 28 | case "http", "https": 29 | resp, _err := http.Get(fd.Uri) 30 | if _err != nil { 31 | return nil, errors.Wrapf(_err, "failed to fetch project file, uri %s", fd.Uri) 32 | } 33 | defer resp.Body.Close() 34 | data, err = io.ReadAll(resp.Body) 35 | case "ipfs": 36 | // ipfs url: ipfs://${endpoint}/${cid} 37 | sh := ipfs.NewIPFS(u.Host) 38 | cid := strings.Split(strings.Trim(u.Path, "/"), "/") 39 | data, err = sh.Cat(cid[0]) 40 | default: 41 | return nil, errors.Errorf("invalid project file uri %s", fd.Uri) 42 | } 43 | if err != nil { 44 | return nil, errors.Wrapf(err, "failed to read project file, uri %s", fd.Uri) 45 | } 46 | 47 | if len(skipHash) == 0 || !skipHash[0] { 48 | h := sha256.New() 49 | if _, err := h.Write(data); err != nil { 50 | return nil, errors.Wrap(err, "failed to generate project file hash") 51 | } 52 | if !bytes.Equal(h.Sum(nil), fd.Hash[:]) { 53 | return nil, errors.New("failed to validate project file hash") 54 | } 55 | } 56 | 57 | return data, nil 58 | } 59 | -------------------------------------------------------------------------------- /util/ipfs/ipfs.go: -------------------------------------------------------------------------------- 1 | package ipfs 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | 8 | shell "github.com/ipfs/go-ipfs-api" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func NewIPFS(endpoint string) *IPFS { 13 | sh := shell.NewShell(endpoint) 14 | return &IPFS{ 15 | endpoint: endpoint, 16 | sh: sh, 17 | } 18 | } 19 | 20 | type IPFS struct { 21 | endpoint string 22 | sh *shell.Shell 23 | } 24 | 25 | func (s *IPFS) AddFile(path string) (string, error) { 26 | content, err := os.ReadFile(path) 27 | if err != nil { 28 | return "", errors.Wrapf(err, "failed to read file: %s", path) 29 | } 30 | return s.AddContent(content) 31 | } 32 | 33 | func (s *IPFS) AddContent(content []byte) (string, error) { 34 | reader := bytes.NewReader(content) 35 | 36 | return s.sh.Add(reader, shell.Pin(true)) 37 | } 38 | 39 | func (s *IPFS) Cat(cid string) ([]byte, error) { 40 | reader, err := s.sh.Cat(cid) 41 | if err != nil { 42 | return nil, errors.Wrapf(err, "failed to read content from ipfs: %s", cid) 43 | } 44 | defer reader.Close() 45 | 46 | return io.ReadAll(reader) 47 | } 48 | -------------------------------------------------------------------------------- /util/ipfs/ipfs_test.go: -------------------------------------------------------------------------------- 1 | package ipfs_test 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "os" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/iotexproject/w3bstream/util/ipfs" 13 | ) 14 | 15 | func TestIPFS(t *testing.T) { 16 | r := require.New(t) 17 | s := ipfs.NewIPFS("ipfs.mainnet.iotex.io") 18 | 19 | content := []byte("test data") 20 | f, err := os.CreateTemp("", "test") 21 | r.Nil(err) 22 | r.NotNil(f) 23 | defer os.RemoveAll(f.Name()) 24 | 25 | cid, err := s.AddFile(f.Name()) 26 | r.NoError(err) 27 | r.NotEqual(cid, "") 28 | t.Log(cid) 29 | 30 | _, err = s.AddFile("not_exists") 31 | r.Error(err) 32 | 33 | cid, err = s.AddContent(content) 34 | r.NoError(err) 35 | r.NotEqual(cid, "") 36 | t.Log(cid) 37 | 38 | content2, err := s.Cat(cid) 39 | r.NoError(err) 40 | r.True(bytes.Equal(content2, content)) 41 | 42 | _, err = s.Cat("notexists") 43 | r.Error(err) 44 | 45 | content, err = s.Cat("QmSiDPZH2xCpuC9g2RLSrVYzfWRxLX4Mgu1cNPUAkZe696") 46 | r.NoError(err) 47 | 48 | h := sha256.New() 49 | _, err = h.Write(content) 50 | r.NoError(err) 51 | 52 | sha256sum := h.Sum(nil) 53 | hashv, err := hex.DecodeString("a543eb8a3e8e1551b0041fb4b7964dba8d07af3cc82490827f8b90d005dd842d") 54 | r.NoError(err) 55 | r.True(bytes.Equal(sha256sum, hashv)) 56 | } 57 | -------------------------------------------------------------------------------- /vm/proto/gen.sh: -------------------------------------------------------------------------------- 1 | protoc --go_out=. --go_opt=paths=source_relative \ 2 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 3 | vm.proto 4 | -------------------------------------------------------------------------------- /vm/proto/vm.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package vm; 3 | option go_package = "./proto"; 4 | 5 | service VM { 6 | rpc NewProject(NewProjectRequest) returns (NewProjectResponse); 7 | rpc ExecuteTask(ExecuteTaskRequest) returns (ExecuteTaskResponse); 8 | } 9 | 10 | message NewProjectRequest { 11 | string projectID = 1; 12 | string projectVersion = 2; 13 | bytes binary = 3; 14 | bytes metadata = 4; 15 | } 16 | 17 | message NewProjectResponse {} 18 | 19 | message ExecuteTaskRequest { 20 | string projectID = 1; 21 | string projectVersion = 2; 22 | bytes taskID = 3; 23 | repeated bytes payloads = 4; 24 | } 25 | 26 | message ExecuteTaskResponse { 27 | bytes result = 1; 28 | } 29 | -------------------------------------------------------------------------------- /vm/vm.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "log/slog" 7 | "strings" 8 | "time" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | "github.com/pkg/errors" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/credentials/insecure" 14 | 15 | "github.com/iotexproject/w3bstream/project" 16 | "github.com/iotexproject/w3bstream/task" 17 | "github.com/iotexproject/w3bstream/util/filefetcher" 18 | "github.com/iotexproject/w3bstream/vm/proto" 19 | ) 20 | 21 | type Handler struct { 22 | vmClients map[uint64]*grpc.ClientConn 23 | } 24 | 25 | func (r *Handler) Handle(tasks []*task.Task, projectConfig *project.Config) ([]byte, error) { 26 | task := tasks[0] 27 | // TODO: load binary before being stored in db 28 | now := time.Now() 29 | bi, err := decodeBinary(projectConfig.Code) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "failed to decode code") 32 | } 33 | slog.Info("fetch circuit code success", "time duration", time.Since(now).String()) 34 | now = time.Now() 35 | metadata, err := decodeBinary(projectConfig.Metadata) 36 | if err != nil { 37 | return nil, errors.Wrap(err, "failed to decode metadata") 38 | } 39 | slog.Info("fetch circuit metadata success", "time duration", time.Since(now).String()) 40 | taskPayload, err := loadPayload(tasks, projectConfig) 41 | if err != nil { 42 | return nil, errors.Wrap(err, "failed to load payload") 43 | } 44 | conn, ok := r.vmClients[projectConfig.VMTypeID] 45 | if !ok { 46 | return nil, errors.Errorf("unsupported vm type id %d", projectConfig.VMTypeID) 47 | } 48 | cli := proto.NewVMClient(conn) 49 | if _, err := cli.NewProject(context.Background(), &proto.NewProjectRequest{ 50 | ProjectID: task.ProjectID.String(), 51 | ProjectVersion: task.ProjectVersion, 52 | Binary: bi, 53 | Metadata: metadata, 54 | }); err != nil { 55 | slog.Error("failed to new project", "project_id", tasks[0].ProjectID, "err", err) 56 | return nil, errors.Wrap(err, "failed to create vm instance") 57 | } 58 | 59 | resp, err := cli.ExecuteTask(context.Background(), &proto.ExecuteTaskRequest{ 60 | ProjectID: task.ProjectID.String(), 61 | ProjectVersion: task.ProjectVersion, 62 | TaskID: task.ID[:], 63 | Payloads: [][]byte{taskPayload}, 64 | }) 65 | if err != nil { 66 | slog.Error("failed to execute task", "project_id", task.ProjectID, "vm_type", projectConfig.VMTypeID, 67 | "task_id", task.ID, "binary", projectConfig.Code, "payloads", task.Payload, "err", err) 68 | return nil, errors.Wrap(err, "failed to execute vm instance") 69 | } 70 | slog.Info("proof", "proof", hexutil.Encode(resp.Result)) 71 | 72 | return resp.Result, nil 73 | } 74 | 75 | // TODO: validate fetched file with hash 76 | func decodeBinary(b string) ([]byte, error) { 77 | if strings.Contains(b, "http") || 78 | strings.Contains(b, "ipfs") { 79 | fd := filefetcher.Filedescriptor{Uri: b} 80 | skipHash := true 81 | return fd.FetchFile(skipHash) 82 | } 83 | return hex.DecodeString(b) 84 | } 85 | 86 | func NewHandler(vmEndpoints map[uint64]string) (*Handler, error) { 87 | clients := map[uint64]*grpc.ClientConn{} 88 | for t, e := range vmEndpoints { 89 | conn, err := grpc.NewClient(e, grpc.WithTransportCredentials(insecure.NewCredentials())) 90 | if err != nil { 91 | return nil, errors.Wrap(err, "failed to new grpc client") 92 | } 93 | clients[t] = conn 94 | } 95 | return &Handler{ 96 | vmClients: clients, 97 | }, nil 98 | } 99 | --------------------------------------------------------------------------------