├── .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 |
--------------------------------------------------------------------------------