├── .cargo └── config.toml ├── .develatus-apparatus.mjs ├── .dockerignore ├── .env.example ├── .github └── workflows │ ├── autogen.yml │ ├── coverage.yml │ ├── docker.sh │ ├── docker.yml │ ├── github-ops.yml │ ├── lint.yml │ ├── pi-eth-transfer.yml │ ├── pi-native-withdraw.yml │ ├── super-eth-transfer.yml │ ├── super-native-withdraw.yml │ └── super-worst-case-mload.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── common ├── Cargo.toml └── src │ ├── json_rpc.rs │ ├── lib.rs │ └── prover.rs ├── contracts ├── Proxy.sol ├── ZkEvmBridgeEvents.sol ├── ZkEvmL1Bridge.sol ├── ZkEvmL2MessageDeliverer.sol ├── ZkEvmL2MessageDispatcher.sol ├── ZkEvmMagicNumbers.sol ├── ZkEvmMessageDelivererBase.sol ├── ZkEvmMessageDispatcher.sol ├── ZkEvmStorage.sol ├── ZkEvmUtils.sol ├── generated │ ├── .gitignore │ └── CircuitConfig.sol ├── interfaces │ ├── ICrossDomainMessenger.sol │ ├── IZkEvmMessageDelivererBase.sol │ ├── IZkEvmMessageDelivererWithProof.sol │ └── IZkEvmMessageDispatcher.sol ├── optimism │ ├── L1OptimismBridge.sol │ ├── L2OptimismBridge.sol │ └── OptimismWrapper.sol ├── templates │ ├── CommonBlockOperations.sol │ ├── HeaderUtil.sol │ ├── InstanceVerifier.sol │ ├── PatriciaValidator.sol │ ├── PublicInput.sol │ ├── mpt.yul │ ├── rlp.yul │ └── utils.yul └── tests │ └── ZkEvmTest.sol ├── coordinator ├── .gitignore ├── Cargo.toml ├── src │ ├── bin │ │ └── coordinator.rs │ ├── config.rs │ ├── faucet.rs │ ├── lib.rs │ ├── macros.rs │ ├── shared_state.rs │ ├── structs.rs │ └── utils.rs └── tests │ ├── chain.rs │ ├── commitment │ └── mod.rs │ ├── common │ └── mod.rs │ ├── deploy.rs │ ├── misc.rs │ ├── patricia.rs │ ├── patricia │ ├── test-data-0000000000000000000000000000000000020000.json │ ├── test-data-68b3465833fb72a70ecdf485e0e4c7bd8665fc45.json │ └── test-data-a5409ec958c83c3f309868babaca7c86dcb077c1.json │ ├── verifier.rs │ ├── verifier │ ├── 0-0.json.xz │ ├── 1-5.json.xz │ ├── 2-1.json.xz │ ├── 3-3072.json.xz │ └── 4-1024.json.xz │ └── worst_case.rs ├── dev ├── Cargo.toml ├── src │ ├── bytecode.rs │ ├── genesis.rs │ └── lib.rs └── tests │ └── autogen.rs ├── docker-compose-perf.yml ├── docker-compose-pub.yml ├── docker-compose.yml ├── docker ├── coordinator │ └── Dockerfile ├── dev │ └── Dockerfile ├── geth │ ├── Dockerfile │ ├── init.sh │ └── templates │ │ ├── l1-testnet.json │ │ └── l2-testnet.json ├── prover │ └── Dockerfile └── web │ ├── Dockerfile │ └── nginx.conf ├── docs └── README.md ├── prover ├── Cargo.toml ├── src │ ├── aggregation_circuit.rs │ ├── bin │ │ ├── gen_params.rs │ │ ├── prover_cmd.rs │ │ └── prover_rpcd.rs │ ├── circuit_autogen.rs │ ├── circuit_witness.rs │ ├── circuits.rs │ ├── lib.rs │ ├── server.rs │ ├── shared_state.rs │ └── utils.rs └── tests │ ├── autogen.rs │ ├── proverd.rs │ └── verifier.rs ├── rust-toolchain ├── rustfmt.toml └── scripts ├── autogen.sh ├── ci_autogen_commit.sh ├── ci_commit_errors.sh ├── compile_contracts.sh ├── dev.sh ├── get_block_fixtures.sh ├── heavy_ci.sh ├── lint.sh ├── patch_genesis.mjs ├── purge_chain.sh ├── rpc_prover.sh ├── test_coverage.sh └── test_prover.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os="macos")'] 2 | rustflags = ["-C", "link-args=-framework CoreFoundation -framework Security"] -------------------------------------------------------------------------------- /.develatus-apparatus.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | artifactsPath: 'build/contracts', 3 | proxyPort: 8545, 4 | rpcUrl: process.env.RPC, 5 | fuzzyMatchFactor: 0.8, 6 | ignore: /(mocks|tests)\/.*\.sol/, 7 | } 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !common/ 3 | !coordinator/ 4 | !prover/ 5 | !dev/ 6 | !docker/ 7 | !Cargo.lock 8 | !Cargo.toml 9 | !rust-toolchain 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MINER_PRIV_KEY=2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200 2 | MINER_ADDRESS=df08f82de32b8d460adbe8d72043e3a7e25a3b39 3 | -------------------------------------------------------------------------------- /.github/workflows/autogen.yml: -------------------------------------------------------------------------------- 1 | name: Autogen 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | 7 | jobs: 8 | gen: 9 | if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'allow-autogen') 10 | timeout-minutes: 7200 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 13 | cancel-in-progress: true 14 | runs-on: ["${{github.run_id}}", self-hosted, r6a.24xlarge] 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | ref: ${{ github.head_ref }} 19 | fetch-depth: 0 20 | 21 | - name: Setup 22 | run: cp .env.example .env 23 | 24 | - name: Build docker images 25 | run: | 26 | docker buildx create --name mybuilder --use || echo 'skip' 27 | docker compose build dev 28 | 29 | - name: Contracts 30 | run: | 31 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c './scripts/compile_contracts.sh' 32 | 33 | - name: Circuit config 34 | run: | 35 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c './scripts/autogen.sh autogen_circuit_config' 36 | 37 | - name: Cargo fmt 38 | run: | 39 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'cargo fmt --all' 40 | 41 | - name: EVM & Aggregation verifier for Super 42 | run: | 43 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'FAST=1 ./scripts/autogen.sh autogen_verifier_super' 44 | 45 | - name: EVM & Aggregation verifier for Pi 46 | run: | 47 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'FAST=1 ./scripts/autogen.sh autogen_verifier_pi' 48 | 49 | - name: Patch genesis templates 50 | run: docker run --rm -v $(pwd):/host -w /host node:lts-alpine scripts/patch_genesis.mjs 51 | 52 | - name: Commit 53 | run: ./scripts/ci_autogen_commit.sh 54 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [synchronize, opened, reopened, ready_for_review] 9 | 10 | jobs: 11 | main: 12 | if: github.event.pull_request.draft == false 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 15 | cancel-in-progress: true 16 | runs-on: ["${{github.run_id}}", self-hosted, r6a.2xlarge] 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | ref: ${{ (github.event.pull_request && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }} 21 | 22 | - name: Setup 23 | run: cp .env.example .env 24 | 25 | - name: Build docker images 26 | run: | 27 | docker compose down -v --remove-orphans || true 28 | docker buildx create --name mybuilder --use || echo 'skip' 29 | docker compose build bootnode dev 30 | 31 | - name: Compile Contracts 32 | run: | 33 | docker compose run --no-TTY --rm --entrypoint bash dev -c './scripts/compile_contracts.sh' 34 | 35 | - name: Run coordinator tests 36 | run: | 37 | docker compose run --no-TTY --rm --entrypoint bash dev -c './scripts/test_coverage.sh' 38 | 39 | - name: Upload Coverage 40 | if: success() 41 | uses: coverallsapp/github-action@1.1.3 42 | with: 43 | github-token: ${{ secrets.GITHUB_TOKEN }} 44 | path-to-lcov: build/coverage-report.lcov 45 | git-branch: ${{ (github.event.pull_request && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }} 46 | git-commit: ${{ (github.event.pull_request && github.event.pull_request.head.sha) || github.sha }} 47 | -------------------------------------------------------------------------------- /.github/workflows/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | tag=$(git tag --points-at HEAD) 6 | 7 | if [ -z "$tag" ]; then 8 | tag='latest' 9 | fi 10 | 11 | docker buildx create --name mybuilder --use || echo 'skip' 12 | docker buildx inspect --bootstrap 13 | 14 | dockerfile="docker/${TARGET}/Dockerfile" 15 | path=$(dirname "${dockerfile}") 16 | ext=${path##*/} 17 | image="ghcr.io/$GITHUB_REPOSITORY/$ext" 18 | 19 | docker buildx build \ 20 | --progress plain \ 21 | --cache-from "type=registry,ref=${image}-ci-cache:latest" \ 22 | --cache-to "type=registry,ref=${image}-ci-cache:latest,mode=max" \ 23 | --compress \ 24 | --push \ 25 | --platform "$PLATFORM" \ 26 | -t "$image:$tag" \ 27 | -f "${dockerfile}" . 28 | docker buildx imagetools inspect "$image:$tag" 29 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | on: 3 | workflow_dispatch: 4 | workflow_run: 5 | workflows: [coverage] 6 | types: [completed] 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' 13 | runs-on: ["${{github.run_id}}", self-hosted, r6a.xlarge, "${{matrix.TARGET}}"] 14 | strategy: 15 | matrix: 16 | TARGET: ['coordinator', 'web', 'prover', 'geth', 'dev'] 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Login to ghcr.io 21 | env: 22 | PAT: ${{ secrets.GITHUB_TOKEN }} 23 | run: printf "$PAT" | docker login --username _ --password-stdin ghcr.io 24 | 25 | - name: Build and push Docker images 26 | env: 27 | TARGET: ${{ matrix.TARGET }} 28 | PLATFORM: 'linux/amd64,linux/arm64' 29 | run: ./.github/workflows/docker.sh 30 | -------------------------------------------------------------------------------- /.github/workflows/github-ops.yml: -------------------------------------------------------------------------------- 1 | name: github-ops 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_run: 6 | workflows: ["*"] 7 | types: 8 | - requested 9 | - completed 10 | 11 | concurrency: 12 | group: ${{ github.workflow }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | ping: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 1 19 | steps: 20 | - name: Ping runner workflow 21 | env: 22 | # only needs public repo scope 23 | GH_OPS_PAT: ${{ secrets.GH_OPS_PAT }} 24 | run: | 25 | curl -H "authorization: token $GH_OPS_PAT" 'https://api.github.com/repos/privacy-scaling-explorations/github-ops/dispatches' -d '{"event_type":"zkevm-chain"}' 26 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | # We only run these lints on trial-merges of PRs to reduce noise. 4 | on: 5 | pull_request: 6 | types: [synchronize, opened, reopened, ready_for_review] 7 | push: 8 | branches: 9 | - main 10 | - master 11 | 12 | jobs: 13 | lint: 14 | if: github.event.pull_request.draft == false 15 | 16 | timeout-minutes: 30 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions-rs/toolchain@v1 22 | with: 23 | components: rustfmt, clippy 24 | override: false 25 | 26 | # Go cache for building geth-utils 27 | - name: Go cache 28 | uses: actions/cache@v3 29 | with: 30 | path: | 31 | ~/.cache/go-build 32 | ~/go/pkg/mod 33 | key: ${{ runner.os }}-go-${{ hashFiles('**/Cargo.lock') }} 34 | restore-keys: | 35 | ${{ runner.os }}-go- 36 | 37 | - name: Cargo cache 38 | uses: actions/cache@v3 39 | with: 40 | path: | 41 | ~/.cargo/bin/ 42 | ~/.cargo/registry/index/ 43 | ~/.cargo/registry/cache/ 44 | ~/.cargo/git/db/ 45 | target/ 46 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 47 | 48 | - name: Run cargo clippy 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: clippy 52 | args: --all-features --all-targets -- -D warnings 53 | 54 | - name: Run cargo fmt 55 | uses: actions-rs/cargo@v1 56 | with: 57 | command: fmt 58 | args: --all -- --check 59 | -------------------------------------------------------------------------------- /.github/workflows/pi-eth-transfer.yml: -------------------------------------------------------------------------------- 1 | name: Pi Circuit aggregation eth_transfer 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | 7 | jobs: 8 | test: 9 | if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'ci-pi-eth-transfer') 10 | timeout-minutes: 7200 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 13 | cancel-in-progress: true 14 | runs-on: ["${{github.run_id}}", self-hosted, r6a.24xlarge] 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup 19 | run: cp .env.example .env 20 | 21 | - name: Build docker images 22 | run: | 23 | docker compose down -v --remove-orphans || true 24 | docker buildx create --name mybuilder --use || echo 'skip' 25 | docker compose build bootnode dev 26 | 27 | - name: Pi Circuit aggregation eth_transfer 28 | if: always() 29 | run: | 30 | docker compose -f docker-compose.yml -f docker-compose-perf.yml run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'COORDINATOR_AGGREGATE_PROOF=true COORDINATOR_CIRCUIT_NAME=pi ./scripts/heavy_ci.sh eth_transfer' 31 | ./scripts/ci_commit_errors.sh pi-ag-eth_transfer 32 | -------------------------------------------------------------------------------- /.github/workflows/pi-native-withdraw.yml: -------------------------------------------------------------------------------- 1 | name: Pi Circuit aggregation native_withdraw 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | 7 | jobs: 8 | test: 9 | if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'ci-pi-native-withdraw') 10 | timeout-minutes: 7200 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 13 | cancel-in-progress: true 14 | runs-on: ["${{github.run_id}}", self-hosted, r6a.24xlarge] 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup 19 | run: cp .env.example .env 20 | 21 | - name: Build docker images 22 | run: | 23 | docker compose down -v --remove-orphans || true 24 | docker buildx create --name mybuilder --use || echo 'skip' 25 | docker compose build bootnode dev 26 | 27 | - name: Pi Circuit aggregation native_withdraw 28 | if: always() 29 | run: | 30 | docker compose -f docker-compose.yml -f docker-compose-perf.yml run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'COORDINATOR_AGGREGATE_PROOF=true COORDINATOR_CIRCUIT_NAME=pi ./scripts/heavy_ci.sh native_withdraw' 31 | ./scripts/ci_commit_errors.sh pi-ag-native_withdraw 32 | -------------------------------------------------------------------------------- /.github/workflows/super-eth-transfer.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation eth_transfer 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | 7 | jobs: 8 | test: 9 | if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'ci-super-eth-transfer') 10 | timeout-minutes: 7200 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 13 | cancel-in-progress: true 14 | runs-on: ["${{github.run_id}}", self-hosted, r6a.24xlarge] 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup 19 | run: cp .env.example .env 20 | 21 | - name: Build docker images 22 | run: | 23 | docker compose down -v --remove-orphans || true 24 | docker buildx create --name mybuilder --use || echo 'skip' 25 | docker compose build bootnode dev 26 | 27 | - name: Super Circuit aggregation eth_transfer 28 | if: always() 29 | run: | 30 | docker compose -f docker-compose.yml -f docker-compose-perf.yml run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'COORDINATOR_AGGREGATE_PROOF=true COORDINATOR_CIRCUIT_NAME=super ./scripts/heavy_ci.sh eth_transfer' 31 | ./scripts/ci_commit_errors.sh super-ag-eth_transfer 32 | -------------------------------------------------------------------------------- /.github/workflows/super-native-withdraw.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation native_withdraw 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | 7 | jobs: 8 | test: 9 | if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'ci-super-native-withdraw') 10 | timeout-minutes: 7200 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 13 | cancel-in-progress: true 14 | runs-on: ["${{github.run_id}}", self-hosted, r6a.48xlarge] 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup 19 | run: cp .env.example .env 20 | 21 | - name: Build docker images 22 | run: | 23 | docker compose down -v --remove-orphans || true 24 | docker buildx create --name mybuilder --use || echo 'skip' 25 | docker compose build bootnode dev 26 | 27 | - name: Super Circuit aggregation native_withdraw 28 | if: always() 29 | run: | 30 | docker compose -f docker-compose.yml -f docker-compose-perf.yml run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'COORDINATOR_AGGREGATE_PROOF=true COORDINATOR_CIRCUIT_NAME=super ./scripts/heavy_ci.sh native_withdraw' 31 | ./scripts/ci_commit_errors.sh super-ag-native_withdraw 32 | -------------------------------------------------------------------------------- /.github/workflows/super-worst-case-mload.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation worst_case_mload 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | 7 | jobs: 8 | test: 9 | if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'ci-super-worst-case-mload') 10 | timeout-minutes: 7200 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 13 | cancel-in-progress: true 14 | runs-on: ["${{github.run_id}}", self-hosted, r6a.48xlarge] 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup 19 | run: cp .env.example .env 20 | 21 | - name: Build docker images 22 | run: | 23 | docker compose down -v --remove-orphans || true 24 | docker buildx create --name mybuilder --use || echo 'skip' 25 | docker compose build bootnode dev 26 | 27 | - name: Super Circuit aggregation worst_case_mload 28 | if: always() 29 | run: | 30 | docker compose -f docker-compose.yml -f docker-compose-perf.yml run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'COORDINATOR_AGGREGATE_PROOF=true COORDINATOR_CIRCUIT_NAME=super ./scripts/heavy_ci.sh worst_case_mload' 31 | ./scripts/ci_commit_errors.sh super-ag-worst_case_mload 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.env 2 | build/ 3 | target/ 4 | .idea/ 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "coordinator", 4 | "prover", 5 | "dev" 6 | ] 7 | 8 | [profile.release] 9 | opt-level = 3 10 | lto = "thin" 11 | 12 | [patch."https://github.com/privacy-scaling-explorations/halo2.git"] 13 | halo2_proofs = { git = "https://github.com/pinkiebell/halo2.git", rev = "2f530b040fc6297a40af6cc8f374cceeb87a628a", package = "halo2_proofs" } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Coverage Status](https://coveralls.io/repos/github/privacy-scaling-explorations/zkevm-chain/badge.svg?branch=master)](https://coveralls.io/github/privacy-scaling-explorations/zkevm-chain?branch=master) 2 | 3 | ## Structure 4 | 5 | |Path|Description| 6 | |-|-| 7 | |`coordinator/`|coordinator daemon| 8 | |`contracts/`|l1/l2 bridge contracts| 9 | |`docker/`|dockerfiles for various purposes| 10 | |`scripts/`|helpful scripts| 11 | 12 | ## Setup 13 | `cp .env.example .env` and edit the values. The account you specify in that file will be the miner of the clique network and will have ETH allocated in the genesis block. 14 | 15 | If you make changes to the genesis file, then you have to delete the existing chain via `docker compose down --volumes` - this will delete any volumes associated with this setup. 16 | Use `DOCKER_BUILDKIT=1 docker compose up` to start the chain. 17 | 18 | You can use `./scripts/dev.sh` to start a local dev environment without the coordinator-service that drops you into a shell. Useful if you want to work on the `coordinator/` daemon. 19 | 20 | ## Developer workflows 21 | ###### Testing the coordinator & prover with `cargo test` 22 | Enter the developer service via `./scripts/dev.sh`. 23 | Inside that shell you can use this wrapper script to build & start the `prover_rpcd` in the background and invoke `cargo test -- eth_transfer`: 24 | ``` 25 | ./scripts/test_prover.sh eth_transfer 26 | ``` 27 | The output of the prover damon will be piped to `PROVER_LOG.txt`. 28 | If you need fixtures for the L2 block with number 1, then use `./scripts/get_block_fixtures.sh $COORDINATOR_L2_RPC_URL 1` to retrieve it for you. 29 | 30 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkevm_common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | eth-types = { git = "https://github.com/pinkiebell/zkevm-circuits.git", branch = "zkevm-chain" } 9 | hyper = { version = "0.14.16", features = ["server"] } 10 | log = "0.4.14" 11 | serde = { version = "1.0.136", features = ["derive"] } 12 | serde_json = "1.0.78" 13 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread"] } 14 | -------------------------------------------------------------------------------- /common/src/json_rpc.rs: -------------------------------------------------------------------------------- 1 | /// Common utilities for json-rpc 2 | use hyper::body::Buf; 3 | use hyper::client::HttpConnector; 4 | use hyper::Body; 5 | use hyper::Request; 6 | use hyper::Uri; 7 | 8 | use serde::de::DeserializeOwned; 9 | use serde::Deserialize; 10 | use serde::Serialize; 11 | 12 | #[derive(Debug, Serialize, Deserialize)] 13 | pub struct JsonRpcError { 14 | pub code: i32, 15 | pub message: String, 16 | } 17 | 18 | #[derive(Debug, Serialize)] 19 | pub struct JsonRpcResponseError { 20 | pub jsonrpc: String, 21 | pub id: serde_json::Value, 22 | pub error: JsonRpcError, 23 | } 24 | 25 | #[derive(Debug, Serialize, Deserialize)] 26 | pub struct JsonRpcResponse { 27 | pub jsonrpc: String, 28 | pub id: serde_json::Value, 29 | pub result: Option, 30 | } 31 | 32 | #[derive(Debug, Serialize, Deserialize)] 33 | pub struct JsonRpcRequest { 34 | pub jsonrpc: String, 35 | pub id: serde_json::Value, 36 | pub method: String, 37 | pub params: T, 38 | } 39 | 40 | /// Invokes a `json-rpc` request with a timeout of `timeout` ms for the network 41 | /// and deserialize part. 42 | pub async fn jsonrpc_request_client( 43 | timeout: u64, 44 | client: &hyper::Client, 45 | uri: &Uri, 46 | method: &str, 47 | params: T, 48 | ) -> Result { 49 | #[derive(Debug, Deserialize)] 50 | struct JsonRpcResponseInternal { 51 | result: Option, 52 | error: Option, 53 | } 54 | 55 | let node_req = Request::post(uri); 56 | let req_obj = JsonRpcRequest { 57 | jsonrpc: "2.0".to_string(), 58 | id: 0.into(), 59 | method: method.to_string(), 60 | params, 61 | }; 62 | 63 | let node_req = node_req 64 | .header(hyper::header::CONTENT_TYPE, "application/json") 65 | .body(Body::from(serde_json::to_vec(&req_obj).unwrap())) 66 | .unwrap(); 67 | 68 | log::trace!("jsonrpc_request_client: {} {}", uri, method); 69 | 70 | let json = tokio::time::timeout(std::time::Duration::from_millis(timeout), async { 71 | let err_str = &uri.to_string(); 72 | let resp = client.request(node_req).await.expect(err_str); 73 | let body = hyper::body::aggregate(resp).await.expect(err_str); 74 | let json: JsonRpcResponseInternal = 75 | serde_json::from_reader(body.reader()).expect(err_str); 76 | 77 | json 78 | }) 79 | .await 80 | .map_err(|e| e.to_string())?; 81 | 82 | if json.error.is_some() { 83 | return Err(json.error.unwrap().message); 84 | } 85 | 86 | if json.result.is_none() { 87 | return Err("no result in response".to_string()); 88 | } 89 | 90 | Ok(json.result.unwrap()) 91 | } 92 | 93 | pub async fn jsonrpc_request( 94 | uri: &Uri, 95 | method: &str, 96 | params: T, 97 | ) -> Result { 98 | let client = hyper::Client::new(); 99 | jsonrpc_request_client(30_000, &client, uri, method, params).await 100 | } 101 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod json_rpc; 2 | pub mod prover; 3 | -------------------------------------------------------------------------------- /common/src/prover.rs: -------------------------------------------------------------------------------- 1 | use eth_types::{Bytes, U256}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Default, Serialize, Deserialize)] 5 | pub struct ProofResult { 6 | /// The halo2 transcript 7 | pub proof: Bytes, 8 | /// Public inputs for the proof 9 | pub instance: Vec, 10 | /// k of circuit parameters 11 | pub k: u8, 12 | /// Randomness used 13 | pub randomness: Bytes, 14 | /// Proofing time 15 | pub duration: u32, 16 | /// Circuit name / identifier 17 | pub label: String, 18 | } 19 | 20 | impl std::fmt::Debug for ProofResult { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | f.debug_struct("Proof") 23 | .field("proof", &format!("{}", &self.proof)) 24 | .field("instance", &self.instance) 25 | .field("k", &self.k) 26 | .field("randomness", &format!("{}", &self.randomness)) 27 | .field("duration", &self.duration) 28 | .finish() 29 | } 30 | } 31 | 32 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 33 | pub struct Proofs { 34 | /// Circuit configuration used 35 | pub config: CircuitConfig, 36 | // Proof result for circuit 37 | pub circuit: ProofResult, 38 | /// Aggregation proof for circuit, if requested 39 | pub aggregation: ProofResult, 40 | /// Gas used. Determines the upper ceiling for circuit parameters 41 | pub gas: u64, 42 | } 43 | 44 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 45 | pub struct ProofRequestOptions { 46 | /// The name of the circuit. 47 | /// "super", "pi" 48 | pub circuit: String, 49 | /// the block number 50 | pub block: u64, 51 | /// the rpc url 52 | pub rpc: String, 53 | /// retry proof computation if error 54 | pub retry: bool, 55 | /// Parameters file or directory to use. 56 | /// Otherwise generates them on the fly. 57 | pub param: Option, 58 | /// Only use MockProver if true. 59 | #[serde(default = "default_bool")] 60 | pub mock: bool, 61 | /// Additionaly aggregates the circuit proof if true 62 | #[serde(default = "default_bool")] 63 | pub aggregate: bool, 64 | /// Runs the MockProver if proofing fails. 65 | #[serde(default = "default_bool")] 66 | pub mock_feedback: bool, 67 | /// Verifies the proof after computation. 68 | #[serde(default = "default_bool")] 69 | pub verify_proof: bool, 70 | } 71 | 72 | impl PartialEq for ProofRequestOptions { 73 | fn eq(&self, other: &Self) -> bool { 74 | self.block == other.block 75 | && self.rpc == other.rpc 76 | && self.param == other.param 77 | && self.circuit == other.circuit 78 | && self.mock == other.mock 79 | && self.aggregate == other.aggregate 80 | } 81 | } 82 | 83 | #[derive(Debug, Clone, Serialize, Deserialize)] 84 | pub struct ProofRequest { 85 | pub options: ProofRequestOptions, 86 | pub result: Option>, 87 | /// A counter to keep track of changes of the `result` field 88 | pub edition: u64, 89 | } 90 | 91 | #[derive(Debug, Serialize, Deserialize)] 92 | pub struct NodeInformation { 93 | pub id: String, 94 | pub tasks: Vec, 95 | } 96 | 97 | #[derive(Debug, Serialize, Deserialize)] 98 | pub struct NodeStatus { 99 | pub id: String, 100 | /// The current active task this instance wants to obtain or is working on. 101 | pub task: Option, 102 | /// `true` if this instance started working on `task` 103 | pub obtained: bool, 104 | } 105 | 106 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 107 | pub struct CircuitConfig { 108 | pub block_gas_limit: usize, 109 | pub max_txs: usize, 110 | pub max_calldata: usize, 111 | pub max_bytecode: usize, 112 | pub max_rws: usize, 113 | pub min_k: usize, 114 | pub pad_to: usize, 115 | pub min_k_aggregation: usize, 116 | pub keccak_padding: usize, 117 | } 118 | 119 | fn default_bool() -> bool { 120 | false 121 | } 122 | -------------------------------------------------------------------------------- /contracts/Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | /// @notice Callforwarding proxy 5 | contract Proxy { 6 | constructor (address initialImplementation) { 7 | assembly { 8 | // stores the initial contract address to forward calls 9 | sstore(not(returndatasize()), initialImplementation) 10 | } 11 | } 12 | 13 | fallback () external payable { 14 | assembly { 15 | // copy all calldata into memory - returndatasize() is a substitute for `0` 16 | calldatacopy(returndatasize(), returndatasize(), calldatasize()) 17 | // keep a copy to be used after the call 18 | let zero := returndatasize() 19 | // call contract address loaded from storage slot with key `uint256(-1)` 20 | let success := delegatecall( 21 | gas(), 22 | sload(not(returndatasize())), 23 | returndatasize(), 24 | calldatasize(), 25 | returndatasize(), 26 | returndatasize() 27 | ) 28 | 29 | // copy all return data into memory 30 | returndatacopy(zero, zero, returndatasize()) 31 | 32 | // if the delegatecall succeeded, then return 33 | if success { 34 | return(zero, returndatasize()) 35 | } 36 | // else revert 37 | revert(zero, returndatasize()) 38 | } 39 | } 40 | 41 | /// @notice For testing purposes only. 42 | function upgrade (address to) external { 43 | assembly { 44 | // stores the contract address to forward calls 45 | sstore(not(returndatasize()), to) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/ZkEvmBridgeEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract ZkEvmBridgeEvents { 5 | event BlockSubmitted(); 6 | event BlockFinalized(bytes32 blockHash); 7 | 8 | event MessageDispatched(address from, address to, uint256 value, uint256 fee, uint256 deadline, uint256 nonce, bytes data); 9 | event MessageDelivered(bytes32 id); 10 | event MessageDropped(bytes32 id); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/ZkEvmL1Bridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './ZkEvmUtils.sol'; 5 | import './ZkEvmMagicNumbers.sol'; 6 | import './ZkEvmBridgeEvents.sol'; 7 | import './ZkEvmMessageDispatcher.sol'; 8 | import './ZkEvmMessageDelivererBase.sol'; 9 | import './interfaces/IZkEvmMessageDelivererWithProof.sol'; 10 | import './generated/PatriciaValidator.sol'; 11 | import './generated/PublicInput.sol'; 12 | import './generated/HeaderUtil.sol'; 13 | import './generated/CircuitConfig.sol'; 14 | 15 | contract ZkEvmL1Bridge is 16 | ZkEvmUtils, 17 | ZkEvmMagicNumbers, 18 | ZkEvmBridgeEvents, 19 | ZkEvmMessageDispatcher, 20 | ZkEvmMessageDelivererBase, 21 | IZkEvmMessageDelivererWithProof, 22 | PatriciaValidator, 23 | PublicInput, 24 | HeaderUtil, 25 | CircuitConfig 26 | { 27 | // TODO: Move storage to static slots 28 | bytes32 public stateRoot; 29 | mapping (bytes32 => bytes32) commitments; 30 | mapping (bytes32 => bytes32) stateRoots; 31 | 32 | function submitBlock (bytes calldata witness) external { 33 | _onlyEOA(); 34 | emit BlockSubmitted(); 35 | 36 | ( 37 | bytes32 parentBlockHash, 38 | bytes32 blockHash, 39 | bytes32 blockStateRoot, 40 | , 41 | uint256 blockGas 42 | ) = _readHeaderParts(witness); 43 | uint256 parentStateRoot = uint256(stateRoots[parentBlockHash]); 44 | uint256 chainId = 99; 45 | (uint256 MAX_TXS, uint256 MAX_CALLDATA) = _getCircuitConfig(blockGas); 46 | 47 | uint256[] memory publicInput = 48 | _buildCommitment(MAX_TXS, MAX_CALLDATA, chainId, parentStateRoot, witness, true); 49 | 50 | bytes32 hash; 51 | assembly { 52 | hash := keccak256(add(publicInput, 32), mul(mload(publicInput), 32)) 53 | } 54 | commitments[bytes32(blockHash)] = hash; 55 | stateRoots[blockHash] = blockStateRoot; 56 | } 57 | 58 | function finalizeBlock (bytes calldata proof) external { 59 | require(proof.length > 32); 60 | 61 | bytes32 blockHash; 62 | assembly { 63 | blockHash := calldataload(proof.offset) 64 | } 65 | bytes32 blockStateRoot = stateRoots[blockHash]; 66 | stateRoot = blockStateRoot; 67 | bytes32 expectedCommitmentHash = commitments[blockHash]; 68 | 69 | assembly { 70 | // verify commitment hash 71 | // TODO: support aggregation circuit 72 | if gt(proof.length, 96) { 73 | let is_aggregated := calldataload(add(proof.offset, 64)) 74 | if iszero(is_aggregated) { 75 | // 5 * 32 76 | let len := 160 77 | let ptr := mload(64) 78 | // skip `blockHash, address, is_aggregated` 79 | calldatacopy(ptr, add(proof.offset, 96), len) 80 | let hash := keccak256(ptr, len) 81 | if iszero(eq(hash, expectedCommitmentHash)) { 82 | revert(0, 0) 83 | } 84 | } 85 | } 86 | 87 | // 32 + 32 + 32 + 5 * 32 88 | if gt(proof.length, 256) { 89 | // call contract at `addr` for proof verification 90 | let offset := add(proof.offset, 32) 91 | let addr := calldataload(offset) 92 | switch extcodesize(addr) 93 | case 0 { 94 | // no code at `addr` 95 | revert(0, 1) 96 | } 97 | 98 | let len := sub(proof.length, 96) 99 | offset := add(offset, 64) 100 | let memPtr := mload(64) 101 | calldatacopy(memPtr, offset, len) 102 | let success := staticcall(gas(), addr, memPtr, len, 0, 0) 103 | switch success 104 | case 0 { 105 | // plonk verification failed 106 | returndatacopy(0, 0, returndatasize()) 107 | revert(0, returndatasize()) 108 | } 109 | } 110 | } 111 | 112 | emit BlockFinalized(blockHash); 113 | } 114 | 115 | /// @inheritdoc IZkEvmMessageDelivererWithProof 116 | function deliverMessageWithProof ( 117 | address from, 118 | address to, 119 | uint256 value, 120 | uint256 fee, 121 | uint256 deadline, 122 | uint256 nonce, 123 | bytes calldata data, 124 | bytes calldata proof 125 | ) external { 126 | _onlyEOA(); 127 | 128 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 129 | (bytes32 proofRoot, bytes32 storageValue) = _validatePatriciaProof( 130 | L2_DISPATCHER, 131 | _PENDING_MESSAGE_KEY(messageHash), 132 | proof 133 | ); 134 | require(proofRoot == stateRoot, 'DMROOT'); 135 | require(storageValue == bytes32(uint256(1)), "DMVAL"); 136 | 137 | _deliverMessage(from, to, value, fee, deadline, nonce, data); 138 | } 139 | 140 | /// @dev For testing purposes 141 | function initGenesis (bytes32 _blockHash, bytes32 _stateRoot) external { 142 | stateRoots[_blockHash] = _stateRoot; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /contracts/ZkEvmL2MessageDeliverer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './ZkEvmMessageDelivererBase.sol'; 5 | import './interfaces/IZkEvmMessageDelivererWithProof.sol'; 6 | import './generated/PatriciaValidator.sol'; 7 | import './ZkEvmStorage.sol'; 8 | import './generated/CommonBlockOperations.sol'; 9 | 10 | contract ZkEvmL2MessageDeliverer is 11 | ZkEvmMessageDelivererBase, 12 | IZkEvmMessageDelivererWithProof, 13 | ZkEvmStorage, 14 | PatriciaValidator, 15 | CommonBlockOperations 16 | { 17 | // TODO: decide on public getters once L1/L2 Inbox is DRY 18 | // state root of L1 19 | bytes32 originStateRoot; 20 | // timestamp of L1 21 | uint256 originTimestamp; 22 | 23 | /// @notice This method imports [stateRoot, timestamp] of a block header. 24 | /// `blockNumber` & `blockHash` must be checked by the L1 verification step(s). 25 | function importBlockHeader (uint256 /*blockNumber*/, bytes32 blockHash, bytes calldata blockHeader) external { 26 | (bytes32 hash, bytes32 stateRoot, uint256 timestamp) = _readBlockHeader(blockHeader); 27 | require(hash == blockHash, 'HASH'); 28 | 29 | originStateRoot = stateRoot; 30 | originTimestamp = timestamp; 31 | } 32 | 33 | /// @inheritdoc IZkEvmMessageDelivererWithProof 34 | function deliverMessageWithProof ( 35 | address from, 36 | address to, 37 | uint256 value, 38 | uint256 fee, 39 | uint256 deadline, 40 | uint256 nonce, 41 | bytes calldata data, 42 | bytes calldata proof 43 | ) external { 44 | _onlyEOA(); 45 | // avoid calling the 'requestETH' or any other 'administrative' functions from L2_DELIVERER 46 | require(to != L2_DISPATCHER, 'TNED'); 47 | 48 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 49 | (bytes32 proofRoot, bytes32 storageValue) = _validatePatriciaProof( 50 | L1_BRIDGE, 51 | _PENDING_MESSAGE_KEY(messageHash), 52 | proof 53 | ); 54 | require(proofRoot == originStateRoot, 'DMROOT'); 55 | require(storageValue == bytes32(uint256(1)), 'DMVAL'); 56 | 57 | _deliverMessage(from, to, value, fee, deadline, nonce, data); 58 | } 59 | 60 | function requestETH (uint256 amount) external { 61 | require(msg.sender == L2_DISPATCHER, 'MSEDS'); 62 | 63 | _transferETH(msg.sender, amount); 64 | } 65 | 66 | receive () external payable { 67 | require(msg.sender == L2_DISPATCHER, 'MSEDS'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/ZkEvmL2MessageDispatcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './ZkEvmMessageDispatcher.sol'; 5 | 6 | contract ZkEvmL2MessageDispatcher is ZkEvmMessageDispatcher { 7 | /// @inheritdoc IZkEvmMessageDispatcher 8 | function dispatchMessage ( 9 | address to, 10 | uint256 fee, 11 | uint256 deadline, 12 | uint256 nonce, 13 | bytes calldata data 14 | ) override external payable returns (bytes32 messageHash) { 15 | // send ETH to L2_DELIVERER 16 | if (msg.value != 0) { 17 | _transferETH(L2_DELIVERER, msg.value); 18 | } 19 | messageHash = _dispatchMessage(to, fee, deadline, nonce, data); 20 | } 21 | 22 | /// @inheritdoc IZkEvmMessageDispatcher 23 | function dropMessage ( 24 | address from, 25 | address to, 26 | uint256 value, 27 | uint256 fee, 28 | uint256 deadline, 29 | uint256 nonce, 30 | bytes calldata data 31 | ) override external { 32 | // acquire ETH from L2_DELIVERER 33 | uint256 amount = value + fee; 34 | if (amount != 0) { 35 | (bool success,) = L2_DELIVERER.call(abi.encodeWithSignature('requestETH(uint256)', amount)); 36 | require(success, 'RQETH'); 37 | } 38 | _dropMessage(from, to, value, fee, deadline, nonce, data); 39 | } 40 | 41 | receive () external payable { 42 | require(msg.sender == L2_DELIVERER, 'MSED'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/ZkEvmMagicNumbers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract ZkEvmMagicNumbers { 5 | uint256 constant MIN_MESSAGE_LOCK_SECONDS = 7200; 6 | address constant L2_DELIVERER = 0x0000000000000000000000000000000000010000; 7 | address constant L2_DISPATCHER = 0x0000000000000000000000000000000000020000; 8 | address constant L1_BRIDGE = 0x936a70C0b28532AA22240dce21f89a8399d6ac60; 9 | 10 | address constant L1_OPTIMISM_WRAPPER = 0x936A70C0b28532aa22240dce21F89a8399d6aC61; 11 | address constant L2_OPTIMISM_WRAPPER = 0x4200000000000000000000000000000000000007; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/ZkEvmMessageDelivererBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './interfaces/IZkEvmMessageDelivererBase.sol'; 5 | import './ZkEvmUtils.sol'; 6 | import './ZkEvmMagicNumbers.sol'; 7 | import './ZkEvmBridgeEvents.sol'; 8 | 9 | contract ZkEvmMessageDelivererBase is 10 | IZkEvmMessageDelivererBase, 11 | ZkEvmUtils, 12 | ZkEvmMagicNumbers, 13 | ZkEvmBridgeEvents 14 | { 15 | address _messageOrigin; 16 | 17 | /// @inheritdoc IZkEvmMessageDelivererBase 18 | function messageOrigin () external view returns (address) { 19 | return _messageOrigin; 20 | } 21 | 22 | /// @dev Common routine for `deliverMessage` or `deliverMessageWithProof`. 23 | function _deliverMessage ( 24 | address from, 25 | address to, 26 | uint256 value, 27 | uint256 fee, 28 | uint256 deadline, 29 | uint256 nonce, 30 | bytes calldata data 31 | ) internal { 32 | require(block.timestamp < deadline, 'DMD'); 33 | 34 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 35 | 36 | if (fee != 0) { 37 | _transferETH(tx.origin, fee); 38 | } 39 | 40 | _messageOrigin = from; 41 | _callAccount(to, value, data); 42 | _messageOrigin = address(0); 43 | 44 | emit MessageDelivered(messageHash); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/ZkEvmMessageDispatcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './interfaces/IZkEvmMessageDispatcher.sol'; 5 | import './ZkEvmUtils.sol'; 6 | import './ZkEvmMagicNumbers.sol'; 7 | import './ZkEvmBridgeEvents.sol'; 8 | import './ZkEvmStorage.sol'; 9 | 10 | contract ZkEvmMessageDispatcher is IZkEvmMessageDispatcher, ZkEvmUtils, ZkEvmMagicNumbers, ZkEvmBridgeEvents, ZkEvmStorage { 11 | /// @inheritdoc IZkEvmMessageDispatcher 12 | function dispatchMessage ( 13 | address to, 14 | uint256 fee, 15 | uint256 deadline, 16 | uint256 nonce, 17 | bytes calldata data 18 | ) virtual external payable returns (bytes32 messageHash) { 19 | messageHash = _dispatchMessage(to, fee, deadline, nonce, data); 20 | } 21 | 22 | /// @inheritdoc IZkEvmMessageDispatcher 23 | function dropMessage ( 24 | address from, 25 | address to, 26 | uint256 value, 27 | uint256 fee, 28 | uint256 deadline, 29 | uint256 nonce, 30 | bytes calldata data 31 | ) virtual external { 32 | _dropMessage(from, to, value, fee, deadline, nonce, data); 33 | } 34 | 35 | function _dispatchMessage ( 36 | address to, 37 | uint256 fee, 38 | uint256 deadline, 39 | uint256 nonce, 40 | bytes calldata data 41 | ) internal returns (bytes32 messageHash) { 42 | require(deadline > block.timestamp + MIN_MESSAGE_LOCK_SECONDS, 'DMD'); 43 | 44 | // assuming underflow check 45 | uint256 value = msg.value - fee; 46 | 47 | messageHash = keccak256(abi.encode(msg.sender, to, value, fee, deadline, nonce, data)); 48 | 49 | bytes32 storageSlot = _PENDING_MESSAGE_KEY(messageHash); 50 | require(_sload(messageHash) == 0, 'DMH'); 51 | _sstore(storageSlot, bytes32(uint256(1))); 52 | 53 | emit MessageDispatched(msg.sender, to, value, fee, deadline, nonce, data); 54 | } 55 | 56 | function _dropMessage ( 57 | address from, 58 | address to, 59 | uint256 value, 60 | uint256 fee, 61 | uint256 deadline, 62 | uint256 nonce, 63 | bytes calldata data 64 | ) internal { 65 | require(block.timestamp > deadline, 'DMD'); 66 | 67 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 68 | 69 | bytes32 storageSlot = _PENDING_MESSAGE_KEY(messageHash); 70 | require(_sload(messageHash) != 0, 'DMH'); 71 | _sstore(storageSlot, 0); 72 | 73 | uint256 amount = value + fee; 74 | if (amount != 0) { 75 | _transferETH(from, amount); 76 | } 77 | 78 | emit MessageDropped(messageHash); 79 | } 80 | 81 | function _sload (bytes32 key) internal view returns (uint256 ret) { 82 | assembly { 83 | ret := sload(key) 84 | } 85 | } 86 | 87 | function _sstore (bytes32 key, bytes32 value) internal { 88 | assembly { 89 | sstore(key, value) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /contracts/ZkEvmStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | /// @notice Holds common functions for storage key calculations. 5 | contract ZkEvmStorage { 6 | function _PENDING_MESSAGE_KEY (bytes32 messageId) internal pure returns (bytes32 ret) { 7 | assembly { 8 | mstore(0, 0x31df76a4) 9 | mstore(32, messageId) 10 | ret := keccak256(0, 64) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/ZkEvmUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract ZkEvmUtils { 5 | /// @dev Revert if caller is not tx sender. 6 | function _onlyEOA () internal view { 7 | require(tx.origin == msg.sender, 'EOA'); 8 | } 9 | 10 | function _transferETH (address receiver, uint256 amount) internal { 11 | (bool success,) = receiver.call{ value: amount }(""); 12 | require(success, 'TETH'); 13 | } 14 | 15 | function _callAccount (address to, uint256 value, bytes calldata data) internal { 16 | assembly { 17 | let ptr := 128 18 | calldatacopy(ptr, data.offset, data.length) 19 | if iszero(call(gas(), to, value, ptr, data.length, 0, 0)) { 20 | returndatacopy(0, 0, returndatasize()) 21 | revert(0, returndatasize()) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/generated/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /contracts/generated/CircuitConfig.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity <0.9.0; 4 | contract CircuitConfig { 5 | function _getCircuitConfig (uint256 blockGasLimit) internal pure returns (uint256, uint256) { 6 | 7 | if (blockGasLimit <= 63000) { 8 | return (3, 10500); 9 | } 10 | 11 | if (blockGasLimit <= 300000) { 12 | return (14, 69750); 13 | } 14 | 15 | revert("CIRCUIT_CONFIG"); 16 | } 17 | } -------------------------------------------------------------------------------- /contracts/interfaces/ICrossDomainMessenger.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >0.5.0 <0.9.0; 3 | 4 | /** 5 | * @title ICrossDomainMessenger 6 | */ 7 | interface ICrossDomainMessenger { 8 | /********** 9 | * Events * 10 | **********/ 11 | 12 | event SentMessage( 13 | address indexed target, 14 | address sender, 15 | bytes message, 16 | uint256 messageNonce, 17 | uint256 gasLimit 18 | ); 19 | event RelayedMessage(bytes32 indexed msgHash); 20 | event FailedRelayedMessage(bytes32 indexed msgHash); 21 | 22 | /************* 23 | * Variables * 24 | *************/ 25 | 26 | function xDomainMessageSender() external view returns (address); 27 | 28 | /******************** 29 | * Public Functions * 30 | ********************/ 31 | 32 | /** 33 | * Sends a cross domain message to the target messenger. 34 | * @param _target Target contract address. 35 | * @param _message Message to send to the target. 36 | * @param _gasLimit Gas limit for the provided message. 37 | */ 38 | function sendMessage( 39 | address _target, 40 | bytes calldata _message, 41 | uint32 _gasLimit 42 | ) external; 43 | } 44 | -------------------------------------------------------------------------------- /contracts/interfaces/IZkEvmMessageDelivererBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | interface IZkEvmMessageDelivererBase { 5 | /// @notice Returns the address of the caller that dispatched the message. 6 | function messageOrigin () external view returns (address); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/interfaces/IZkEvmMessageDelivererWithProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './IZkEvmMessageDelivererBase.sol'; 5 | 6 | interface IZkEvmMessageDelivererWithProof is IZkEvmMessageDelivererBase { 7 | /// @notice Verifies the proof and executes the message. 8 | function deliverMessageWithProof ( 9 | address from, 10 | address to, 11 | uint256 value, 12 | uint256 fee, 13 | uint256 deadline, 14 | uint256 nonce, 15 | bytes calldata data, 16 | bytes calldata proof 17 | ) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/interfaces/IZkEvmMessageDispatcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | interface IZkEvmMessageDispatcher { 5 | /// @notice Dispatch a message to the opposite chain. 6 | /// @param to The address of the account/contract to call and transfer `msg.value - fee`. 7 | /// @param fee Amount to be paid to the account that delivers this message on the destination chain. Deducted from `msg.value`. 8 | /// @param deadline This message is valid **before** the deadline and can be dropped **after** the deadline. In seconds since Unix Epoch. 9 | /// @param nonce A random value that can be used to avoid collisions for identical but distinct messages. Has no other purpose. 10 | /// @param data The calldata to be used when calling `to`. 11 | /// @return messageHash `keccak256(abi.encode(msg.sender, to, value, fee, deadline, nonce, data))`. 12 | /// Please note that only one message with the same hash can be dispatched at the same time. 13 | /// A message hash is not unique in the sense that it can reappear once a previous message was delivered or dropped. 14 | function dispatchMessage ( 15 | address to, 16 | uint256 fee, 17 | uint256 deadline, 18 | uint256 nonce, 19 | bytes calldata data 20 | ) external payable returns (bytes32 messageHash); 21 | 22 | /// @notice Drops a expired message and returns ETH - if any to `from`. 23 | function dropMessage ( 24 | address from, 25 | address to, 26 | uint256 value, 27 | uint256 fee, 28 | uint256 deadline, 29 | uint256 nonce, 30 | bytes calldata data 31 | ) external; 32 | } 33 | -------------------------------------------------------------------------------- /contracts/optimism/L1OptimismBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../interfaces/ICrossDomainMessenger.sol'; 5 | import '../ZkEvmMagicNumbers.sol'; 6 | import './OptimismWrapper.sol'; 7 | 8 | contract L1OptimismBridge is ICrossDomainMessenger, ZkEvmMagicNumbers, OptimismWrapper { 9 | function sendMessage( 10 | address _target, 11 | bytes calldata _message, 12 | uint32 _gasLimit 13 | ) external { 14 | _wrapMessage(L1_BRIDGE, L2_OPTIMISM_WRAPPER, _target, _message, _gasLimit); 15 | } 16 | 17 | function relay (address from, address to, bytes calldata data) external { 18 | _relay(L1_BRIDGE, L2_OPTIMISM_WRAPPER, from, to, data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/optimism/L2OptimismBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../interfaces/ICrossDomainMessenger.sol'; 5 | import '../ZkEvmMagicNumbers.sol'; 6 | import './OptimismWrapper.sol'; 7 | 8 | contract L2OptimisimBridge is ICrossDomainMessenger, ZkEvmMagicNumbers, OptimismWrapper { 9 | function sendMessage( 10 | address _target, 11 | bytes calldata _message, 12 | uint32 _gasLimit 13 | ) external { 14 | _wrapMessage(L2_DISPATCHER, L1_OPTIMISM_WRAPPER, _target, _message, _gasLimit); 15 | } 16 | 17 | function relay (address from, address to, bytes calldata data) external { 18 | _relay(L2_DELIVERER, L1_OPTIMISM_WRAPPER, from, to, data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/optimism/OptimismWrapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../interfaces/ICrossDomainMessenger.sol'; 5 | import '../interfaces/IZkEvmMessageDispatcher.sol'; 6 | import '../interfaces/IZkEvmMessageDelivererWithProof.sol'; 7 | import '../ZkEvmUtils.sol'; 8 | 9 | abstract contract OptimismWrapper is ICrossDomainMessenger, ZkEvmUtils { 10 | address internal constant DEFAULT_XDOMAIN_SENDER = 0x000000000000000000000000000000000000dEaD; 11 | 12 | address xDomainMsgSender; 13 | 14 | function xDomainMessageSender () external view returns (address) { 15 | require( 16 | xDomainMsgSender != DEFAULT_XDOMAIN_SENDER, 17 | 'xDomainMessageSender is not set' 18 | ); 19 | return xDomainMsgSender; 20 | } 21 | 22 | function _wrapMessage ( 23 | address fromBridge, 24 | address toBridge, 25 | address _target, 26 | bytes calldata _message, 27 | uint32 _gasLimit 28 | ) internal { 29 | uint256 fee = 0; 30 | uint256 deadline = block.timestamp + 1 days; 31 | uint256 nonce; 32 | assembly { 33 | nonce := add(gas(), add(difficulty(), timestamp())) 34 | } 35 | 36 | bytes memory data = abi.encodeWithSignature('relay(address,address,bytes)', msg.sender, _target, _message); 37 | IZkEvmMessageDispatcher(fromBridge).dispatchMessage(toBridge, fee, deadline, nonce, data); 38 | 39 | emit SentMessage(_target, msg.sender, _message, nonce, _gasLimit); 40 | } 41 | 42 | function _relay (address bridge, address fromWrapper, address from, address to, bytes calldata data) internal { 43 | require(msg.sender == bridge, 'sender'); 44 | require(IZkEvmMessageDelivererWithProof(bridge).messageOrigin() == fromWrapper, 'message origin'); 45 | 46 | xDomainMsgSender = from; 47 | _callAccount(to, 0, data); 48 | xDomainMsgSender = address(0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/templates/CommonBlockOperations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract CommonBlockOperations { 5 | function _readBlockHeader ( 6 | bytes calldata blockHeader 7 | ) internal pure returns (bytes32 blockHash, bytes32 stateRoot, uint256 _timestamp) { 8 | assembly { 9 | //@INCLUDE:utils.yul 10 | //@INCLUDE:rlp.yul 11 | 12 | // expecting 16 individual items from the block header 13 | let calldataPtr, memStart, nItems, hash := decodeFlat(blockHeader.offset) 14 | 15 | // boundary check 16 | if iszero( eq(calldataPtr, add(blockHeader.offset, blockHeader.length)) ) { 17 | revertWith('BOUNDS') 18 | } 19 | if iszero( eq(nItems, 16) ) { 20 | revertWith('ITEMS') 21 | } 22 | 23 | blockHash := hash 24 | 25 | // at position 3 should be the stateRoot 26 | stateRoot := loadValue(memStart, 3) 27 | 28 | // at position 11 should be the timestamp 29 | _timestamp := loadValue(memStart, 11) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/templates/HeaderUtil.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract HeaderUtil { 5 | function _readHeaderParts ( 6 | bytes calldata blockHeader 7 | ) internal pure returns ( 8 | bytes32 parentHash, 9 | bytes32 blockHash, 10 | bytes32 stateRoot, 11 | uint256 blockNumber, 12 | uint256 blockGasUsed 13 | ) { 14 | assembly { 15 | //@INCLUDE:utils.yul 16 | //@INCLUDE:rlp.yul 17 | 18 | // expecting 16 individual items from the block header 19 | let calldataPtr, memStart, nItems, hash := decodeFlat(blockHeader.offset) 20 | require(eq(nItems, 15), "BLOCK_ITEMS") 21 | 22 | // boundary check 23 | require(lt(calldataPtr, add(blockHeader.offset, blockHeader.length)), "BOUNDS") 24 | 25 | blockHash := hash 26 | parentHash := loadValue(memStart, 0) 27 | stateRoot := loadValue(memStart, 3) 28 | blockNumber := loadValue(memStart, 8) 29 | blockGasUsed := loadValue(memStart, 10) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/templates/PatriciaValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract PatriciaValidator { 5 | /// @dev This function validates a proof from eth_getCode. 6 | /// Intended for non-zero storage slots only. 7 | /// @param account The address of the contract. 8 | /// @param storageKey The storage slot in question. 9 | /// @param proofData Should contain: 10 | /// <1 byte - len of accountProof items> 11 | /// < concat accountProof> 12 | /// < 1 byte - len of storageProof items> 13 | /// < concat storageProof > 14 | /// @return stateRoot The computed state root. Must be checked by the caller. 15 | /// @return storageValue The value of `storageKey`. 16 | function _validatePatriciaProof ( 17 | address account, 18 | bytes32 storageKey, 19 | bytes calldata proofData 20 | ) internal pure returns (bytes32 stateRoot, bytes32 storageValue) { 21 | assembly { 22 | //@INCLUDE:rlp.yul 23 | //@INCLUDE:mpt.yul 24 | //@INCLUDE:utils.yul 25 | 26 | // shared variable names 27 | let storageHash 28 | let encodedPath 29 | let path 30 | let hash 31 | let vlen 32 | // starting point 33 | let ptr := proofData.offset 34 | 35 | { 36 | // account proof 37 | // Note: this doesn't work if there are no intermediate nodes before the leaf. 38 | // This is not possible in practice because of the fact that there must be at least 39 | // 2 accounts in the tree to make a transaction to a existing contract possible. 40 | // Thus, 2 leaves. 41 | let prevHash 42 | let key := keccak_20(account) 43 | // `stateRoot` is a return value and must be checked by the caller 44 | ptr, stateRoot, prevHash, path := walkTree(key, ptr) 45 | 46 | let memStart, nItems 47 | ptr, memStart, nItems, hash := decodeFlat(ptr) 48 | 49 | // the hash of the leaf must match the previous hash from the node 50 | cmp(hash, prevHash, 'ACLEAFH') 51 | 52 | // 2 items 53 | // - encoded path 54 | // - account leaf RLP (4 items) 55 | require(eq(nItems, 2), "ACLEAF") 56 | 57 | encodedPath := loadValue(memStart, 0) 58 | // the calculated path must match the encoded path in the leaf 59 | cmp(path, encodedPath, 'ACROOT') 60 | 61 | // Load the position, length of the second element (RLP encoded) 62 | let leafPtr, leafLen := loadPair(memStart, 1) 63 | leafPtr , memStart, nItems, hash := decodeFlat(leafPtr) 64 | 65 | // the account leaf should contain 4 values, 66 | // we want: 67 | // - storageHash @ 2 68 | require(eq(nItems, 4), "ACLEAFN") 69 | storageHash := loadValue(memStart, 2) 70 | } 71 | 72 | 73 | { 74 | // storage proof 75 | let rootHash 76 | let key := keccak_32(storageKey) 77 | ptr, rootHash, hash, path := walkTree(key, ptr) 78 | 79 | // leaf should contain 2 values 80 | // - encoded path @ 0 81 | // - storageValue @ 1 82 | ptr, hash, encodedPath, storageValue, vlen := hashCompareSelect(ptr, 2, 0, 1) 83 | // the calculated path must match the encoded path in the leaf 84 | cmp(path, encodedPath, 'STLEAF') 85 | 86 | switch rootHash 87 | case 0 { 88 | // in the case that the leaf is the only element, then 89 | // the hash of the leaf must match the value from the account leaf 90 | cmp(hash, storageHash, 'STROOT') 91 | } 92 | default { 93 | // otherwise the root hash of the storage tree 94 | // must match the value from the account leaf 95 | cmp(rootHash, storageHash, 'STROOT') 96 | } 97 | 98 | // storageValue is a return value 99 | storageValue := decodeItem(storageValue, vlen) 100 | } 101 | 102 | // the one and only boundary check 103 | // in case an attacker crafted a malicous payload 104 | // and succeeds in the prior verification steps 105 | // then this should catch any bogus accesses 106 | if iszero( eq(ptr, add(proofData.offset, proofData.length)) ) { 107 | revertWith('BOUNDS') 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /contracts/templates/mpt.yul: -------------------------------------------------------------------------------- 1 | // decodes RLP at `_ptr`. 2 | // reverts if the number of DATA items doesn't match `nValues`. 3 | // returns the RLP data items at pos `v0`, `v1` 4 | // and the size of `v1out` 5 | function hashCompareSelect (_ptr, nValues, v0, v1) -> ptr, hash, v0out, v1out, v1outlen { 6 | ptr := _ptr 7 | 8 | let memStart, nItems 9 | ptr, memStart, nItems, hash := decodeFlat(ptr) 10 | 11 | if iszero( eq(nItems, nValues) ) { 12 | revertWith('NITEMS') 13 | } 14 | 15 | v0out, v1outlen := loadValueLen(memStart, v0) 16 | v1out, v1outlen := loadValueLen(memStart, v1) 17 | } 18 | 19 | // traverses the tree from the root to the node before the leaf. 20 | // Note: `_depth` is untrusted. 21 | function walkTree (key, _ptr) -> ptr, rootHash, expectedHash, path { 22 | ptr := _ptr 23 | 24 | // the number of distinct proofs - 1 25 | // (the leaf is treated differently) 26 | let nNodes := sub(byte(0, calldataload(ptr)), 1) 27 | ptr := add(ptr, 1) 28 | 29 | // keeps track of ascend/descend - however you may look at a tree 30 | let depth 31 | 32 | for { let i := 0 } lt(i, nNodes) { i := add(i, 1) } { 33 | let memStart, nItems, hash 34 | ptr, memStart, nItems, hash := decodeFlat(ptr) 35 | 36 | // first item is considered the root node. 37 | // Otherwise verifies that the hash of the current node 38 | // is the same as the previous choosen one. 39 | switch i 40 | case 0 { 41 | rootHash := hash 42 | } default { 43 | cmp(hash, expectedHash, 'THASH') 44 | } 45 | 46 | switch nItems 47 | case 2 { 48 | // extension node 49 | let value, len 50 | 51 | // load the second item. 52 | // this is the hash of the next node. 53 | value, len := loadValueLen(memStart, 1) 54 | expectedHash := value 55 | 56 | // get the byte length of the first item 57 | // Note: the value itself is not validated 58 | // and it is instead assumed that any invalid 59 | // value is invalidated by comparing the root hash. 60 | let prefixLen := shr(128, mload(memStart)) 61 | depth := add(depth, prefixLen) 62 | } 63 | case 17 { 64 | let bits := sub(252, mul(depth, 4)) 65 | let nibble := and(shr(bits, key), 0xf) 66 | 67 | // load the value at pos `nibble` 68 | let value, len := loadValueLen(memStart, nibble) 69 | 70 | expectedHash := value 71 | depth := add(depth, 1) 72 | } 73 | default { 74 | // everything else is unexpected 75 | revertWith('NODE') 76 | } 77 | } 78 | 79 | // lastly, derive the path of the choosen one (TM) 80 | path := derivePath(key, depth) 81 | } 82 | -------------------------------------------------------------------------------- /contracts/templates/rlp.yul: -------------------------------------------------------------------------------- 1 | // special function for decoding the storage value 2 | // because of the prefix truncation if value > 31 bytes 3 | // see `loadValue` 4 | function decodeItem (word, len) -> ret { 5 | // default 6 | ret := word 7 | 8 | // RLP single byte 9 | if lt(word, 0x80) { 10 | leave 11 | } 12 | 13 | // truncated 14 | if gt(len, 32) { 15 | leave 16 | } 17 | 18 | // value is >= 0x80 and <= 32 bytes. 19 | // `len` should be at least 2 (prefix byte + value) 20 | // otherwise the RLP is malformed. 21 | let bits := mul(len, 8) 22 | // sub 8 bits - the prefix 23 | bits := sub(bits, 8) 24 | let mask := shl(bits, 0xff) 25 | // invert the mask 26 | mask := not(mask) 27 | // should hold the value - prefix byte 28 | ret := and(ret, mask) 29 | } 30 | 31 | // returns the `len` of the whole RLP list at `ptr` 32 | // and the offset for the first value inside the list. 33 | function decodeListLength (ptr) -> len, startOffset { 34 | let firstByte := byte(0, calldataload(ptr)) 35 | 36 | // SHORT LIST 37 | // 0 - 55 bytes 38 | // 0xc0 - 0xf7 39 | if lt(firstByte, 0xf8) { 40 | len := sub(firstByte, 0xbf) 41 | startOffset := add(ptr, 1) 42 | leave 43 | } 44 | 45 | // LONG LIST 46 | // 0xf8 - 0xff 47 | // > 55 bytes 48 | { 49 | let lenOf := sub(firstByte, 0xf7) 50 | if gt(lenOf, 4) { 51 | invalid() 52 | } 53 | 54 | // load the extended length 55 | startOffset := add(ptr, 1) 56 | let extendedLen := calldataload(startOffset) 57 | let bits := sub(256, mul(lenOf, 8)) 58 | extendedLen := shr(bits, extendedLen) 59 | 60 | len := add(extendedLen, lenOf) 61 | len := add(len, 1) 62 | startOffset := add(startOffset, lenOf) 63 | leave 64 | } 65 | } 66 | 67 | // returns the calldata offset of the value and the length in bytes 68 | // for the RLP encoded data item at `ptr`. 69 | // used in `decodeFlat` 70 | function decodeValue (ptr) -> dataLen, valueOffset, isData { 71 | let firstByte := byte(0, calldataload(ptr)) 72 | 73 | // SINGLE BYTE 74 | // 0x00 - 0x7f 75 | if lt(firstByte, 0x80) { 76 | dataLen := 1 77 | valueOffset := ptr 78 | isData := 1 79 | leave 80 | } 81 | 82 | // DATA ITEM 83 | // 0 - 55 bytes long 84 | // 0x80 - 0xb7 85 | if lt(firstByte, 0xb8) { 86 | dataLen := sub(firstByte, 0x80) 87 | valueOffset := add(ptr, 1) 88 | isData := 1 89 | leave 90 | } 91 | 92 | // LONG DATA ITEM 93 | // > 55 bytes 94 | // 0xb8 - 0xbf 95 | if lt(firstByte, 0xc0) { 96 | let lenOf := sub(firstByte, 0xb7) 97 | if gt(lenOf, 4) { 98 | invalid() 99 | } 100 | 101 | // load the extended length 102 | valueOffset := add(ptr, 1) 103 | let extendedLen := calldataload(valueOffset) 104 | let bits := sub(256, mul(lenOf, 8)) 105 | extendedLen := shr(bits, extendedLen) 106 | 107 | dataLen := extendedLen 108 | valueOffset := add(valueOffset, lenOf) 109 | isData := 1 110 | leave 111 | } 112 | 113 | // SHORT LIST 114 | // 0 - 55 bytes 115 | // 0xc0 - 0xf7 116 | if lt(firstByte, 0xf8) { 117 | // intentionally ignored 118 | // dataLen := sub(firstByte, 0xbf) 119 | valueOffset := add(ptr, 1) 120 | leave 121 | } 122 | 123 | // LONG LIST 124 | // 0xf8 - 0xff 125 | // > 55 bytes 126 | { 127 | // the extended length is ignored 128 | dataLen := sub(firstByte, 0xf7) 129 | valueOffset := add(ptr, 1) 130 | leave 131 | } 132 | } 133 | 134 | // decodes all RLP encoded data and stores their DATA items 135 | // [length - 128 bits | calldata offset - 128 bits] in a continous memory region. 136 | // Expects that the RLP starts with a list that defines the length 137 | // of the whole RLP region. 138 | function decodeFlat (_ptr) -> ptr, memStart, nItems, hash { 139 | ptr := _ptr 140 | 141 | // load free memory ptr 142 | // doesn't update the ptr and leaves the memory region dirty 143 | memStart := mload(64) 144 | 145 | let payloadLen, startOffset := decodeListLength(ptr) 146 | // reuse memStart region and hash 147 | calldatacopy(memStart, ptr, payloadLen) 148 | hash := keccak256(memStart, payloadLen) 149 | 150 | let memPtr := memStart 151 | let ptrStop := add(ptr, payloadLen) 152 | ptr := startOffset 153 | 154 | // decode until the end of the list 155 | for {} lt(ptr, ptrStop) {} { 156 | let len, valuePtr, isData := decodeValue(ptr) 157 | ptr := add(len, valuePtr) 158 | 159 | if isData { 160 | // store the length of the data and the calldata offset 161 | let tmp := or(shl(128, len), valuePtr) 162 | mstore(memPtr, tmp) 163 | memPtr := add(memPtr, 32) 164 | } 165 | } 166 | 167 | if iszero(eq(ptr, ptrStop)) { 168 | invalid() 169 | } 170 | 171 | nItems := div( sub(memPtr, memStart), 32 ) 172 | } 173 | 174 | // hashes 32 bytes of `v` 175 | function keccak_32 (v) -> r { 176 | mstore(0, v) 177 | r := keccak256(0, 32) 178 | } 179 | 180 | // hashes the last 20 bytes of `v` 181 | function keccak_20 (v) -> r { 182 | mstore(0, v) 183 | r := keccak256(12, 20) 184 | } 185 | 186 | // prefix gets truncated to 256 bits 187 | // `depth` is untrusted and can lead to bogus 188 | // shifts/masks. In that case, the remaining verification 189 | // steps must fail or lead to an invalid stateRoot hash 190 | // if the proof data is 'spoofed but valid' 191 | function derivePath (key, depth) -> path { 192 | path := key 193 | 194 | let bits := mul(depth, 4) 195 | { 196 | let mask := not(0) 197 | mask := shr(bits, mask) 198 | path := and(path, mask) 199 | } 200 | 201 | // even prefix 202 | let prefix := 0x20 203 | if mod(depth, 2) { 204 | // odd 205 | prefix := 0x3 206 | } 207 | 208 | // the prefix may be shifted outside bounds 209 | // this is intended, see `loadValue` 210 | bits := sub(256, bits) 211 | prefix := shl(bits, prefix) 212 | path := or(prefix, path) 213 | } 214 | 215 | // loads and aligns a value from calldata 216 | // given the `len|offset` stored at `memPtr` 217 | function loadValue (memPtr, idx) -> value { 218 | let tmp := mload(add(memPtr, mul(32, idx))) 219 | // assuming 0xffffff is sufficient for storing calldata offset 220 | let offset := and(tmp, 0xffffff) 221 | let len := shr(128, tmp) 222 | 223 | if gt(len, 31) { 224 | // special case - truncating the value is intended. 225 | // this matches the behavior in `derivePath` that truncates to 256 bits. 226 | offset := add(offset, sub(len, 32)) 227 | value := calldataload(offset) 228 | leave 229 | } 230 | 231 | // everything else is 232 | // < 32 bytes - align the value 233 | let bits := mul( sub(32, len), 8) 234 | value := calldataload(offset) 235 | value := shr(bits, value) 236 | } 237 | 238 | // loads and aligns a value from calldata 239 | // given the `len|offset` stored at `memPtr` 240 | // Same as `loadValue` except it returns also the size 241 | // of the value. 242 | function loadValueLen (memPtr, idx) -> value, len { 243 | let tmp := mload(add(memPtr, mul(32, idx))) 244 | // assuming 0xffffff is sufficient for storing calldata offset 245 | let offset := and(tmp, 0xffffff) 246 | len := shr(128, tmp) 247 | 248 | if gt(len, 31) { 249 | // special case - truncating the value is intended. 250 | // this matches the behavior in `derivePath` that truncates to 256 bits. 251 | offset := add(offset, sub(len, 32)) 252 | value := calldataload(offset) 253 | leave 254 | } 255 | 256 | // everything else is 257 | // < 32 bytes - align the value 258 | let bits := mul( sub(32, len), 8) 259 | value := calldataload(offset) 260 | value := shr(bits, value) 261 | } 262 | 263 | function loadPair (memPtr, idx) -> offset, len { 264 | let tmp := mload(add(memPtr, mul(32, idx))) 265 | // assuming 0xffffff is sufficient for storing calldata offset 266 | offset := and(tmp, 0xffffff) 267 | len := shr(128, tmp) 268 | } 269 | -------------------------------------------------------------------------------- /contracts/templates/utils.yul: -------------------------------------------------------------------------------- 1 | // function Error(string) 2 | function revertWith (msg) { 3 | mstore(0, shl(224, 0x08c379a0)) 4 | mstore(4, 32) 5 | mstore(68, msg) 6 | let msgLen 7 | for {} msg {} { 8 | msg := shl(8, msg) 9 | msgLen := add(msgLen, 1) 10 | } 11 | mstore(36, msgLen) 12 | revert(0, 100) 13 | } 14 | 15 | function require (cond, msg) { 16 | switch cond 17 | case 0 { 18 | revertWith(msg) 19 | } 20 | } 21 | 22 | // reverts with `msg` if `a != b`. 23 | function cmp (a, b, msg) { 24 | switch eq(a, b) 25 | case 0 { 26 | revertWith(msg) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/tests/ZkEvmTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../generated/PatriciaValidator.sol'; 5 | import '../generated/InstanceVerifier.sol'; 6 | import '../generated/PublicInput.sol'; 7 | 8 | contract ZkEvmTest is PatriciaValidator, InstanceVerifier, PublicInput { 9 | function testPatricia ( 10 | address account, 11 | bytes32 storageKey, 12 | bytes calldata proofData 13 | ) external pure returns (bytes32 stateRoot, bytes32 storageValue) { 14 | return _validatePatriciaProof(account, storageKey, proofData); 15 | } 16 | 17 | function testPublicInput( 18 | uint256 MAX_TXS, 19 | uint256 MAX_CALLDATA, 20 | uint256 chainId, 21 | uint256 parentStateRoot, 22 | bytes calldata witness 23 | ) external pure returns (uint256[] memory) { 24 | (uint256[] memory publicInput,) = 25 | _buildTable(MAX_TXS, MAX_CALLDATA, chainId, parentStateRoot, witness, false); 26 | 27 | // Use of assembly here because it otherwise does 28 | // a whole copy of `publicInput`. 29 | assembly { 30 | let ptr := sub(publicInput, 32) 31 | mstore(ptr, 0x20) 32 | let len := add(mul(mload(publicInput), 32), 64) 33 | return(ptr, len) 34 | } 35 | } 36 | 37 | function testPublicInputCommitment( 38 | uint256 MAX_TXS, 39 | uint256 MAX_CALLDATA, 40 | uint256 chainId, 41 | uint256 parentStateRoot, 42 | bytes calldata witness 43 | ) external pure returns (uint256[] memory) { 44 | uint256[] memory publicInput = 45 | _buildCommitment(MAX_TXS, MAX_CALLDATA, chainId, parentStateRoot, witness, true); 46 | 47 | return publicInput; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /coordinator/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | prover_cmd 3 | -------------------------------------------------------------------------------- /coordinator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coordinator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.0.15", features = ["derive", "env"] } 10 | env_logger = "0.9.0" 11 | ethers-core = "0.17.0" 12 | ethers-signers = "0.17.0" 13 | hyper = { version = "0.14.16", features = ["client", "server", "http1", "http2", "runtime"] } 14 | log = "0.4.14" 15 | rand = "0.8.4" 16 | serde = { version = "1.0.136", features = ["derive"] } 17 | serde_json = "1.0.78" 18 | serde_with = "2.0.1" 19 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread", "time"] } 20 | zkevm_common = { path = "../common" } 21 | 22 | [dev-dependencies] 23 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread", "time", "parking_lot"] } 24 | rust-lzma = "0.5.1" 25 | -------------------------------------------------------------------------------- /coordinator/src/config.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use ethers_core::types::Address; 3 | use hyper::Uri; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_with::{serde_as, DisplayFromStr}; 6 | use std::net::SocketAddr; 7 | 8 | #[serde_as] 9 | #[derive(Parser, Deserialize, Serialize, Clone, Debug)] 10 | #[clap(version, about)] 11 | /// zkEVM coordinator, coordinates between the prover and the block production and relays between the bridge contracts in L1 and L2. 12 | pub struct Config { 13 | #[clap(long, env = "COORDINATOR_RPC_SERVER_NODES")] 14 | /// Address in the form of host:port of the L2 rpc node(s). Can resolve to multiple addresses. 15 | pub rpc_server_nodes: String, 16 | 17 | #[clap(long, env = "COORDINATOR_ENABLE_FAUCET")] 18 | /// Enables faucet to send eth to L1 wallet. 19 | pub enable_faucet: bool, 20 | 21 | #[clap(long, env = "COORDINATOR_LISTEN")] 22 | /// Address for the coordinator to listen to, in the format of ip:port. 23 | pub listen: SocketAddr, 24 | 25 | #[clap(long, env = "COORDINATOR_DUMMY_PROVER")] 26 | /// Enables dummy prover, so request will not be sent to the actual prover. 27 | pub dummy_prover: bool, 28 | 29 | #[clap(long, env = "COORDINATOR_MOCK_PROVER", default_value_t = false)] 30 | /// Only use the mock prover for proof requests. 31 | pub mock_prover: bool, 32 | 33 | #[clap(long, env = "COORDINATOR_MOCK_PROVER_IF_ERROR", default_value_t = true)] 34 | /// Run the mock prover if a proof request fails. 35 | pub mock_prover_if_error: bool, 36 | 37 | #[clap(long, env = "COORDINATOR_VERIFY_PROOF", default_value_t = false)] 38 | /// Enable verification of the proof. 39 | pub verify_proof: bool, 40 | 41 | #[clap(long, env = "COORDINATOR_L1_RPC_URL")] 42 | #[serde_as(as = "DisplayFromStr")] 43 | /// L1 RPC node URL format. 44 | pub l1_rpc_url: Uri, 45 | 46 | #[clap(long, env = "COORDINATOR_L1_BRIDGE")] 47 | /// Ethereum address of the L1 bridge contract. 48 | pub l1_bridge: Address, 49 | 50 | #[clap(long, env = "COORDINATOR_L1_PRIV")] 51 | /// Private key for Ethereum L1 wallet. 52 | pub l1_priv: String, 53 | 54 | #[clap(long, env = "COORDINATOR_L2_RPC_URL")] 55 | #[serde_as(as = "DisplayFromStr")] 56 | /// L2 RPC node in http URL format. 57 | pub l2_rpc_url: Uri, 58 | 59 | #[clap(long, env = "COORDINATOR_PROVER_RPCD_URL")] 60 | #[serde_as(as = "DisplayFromStr")] 61 | /// Prover RPC node URL. 62 | pub prover_rpcd_url: Uri, 63 | 64 | #[clap(long, env = "COORDINATOR_PARAMS_PATH")] 65 | /// Parameters file or directory to use for the prover requests. 66 | /// Otherwise generates them on the fly. 67 | pub params_path: Option, 68 | 69 | #[clap(long, env = "COORDINATOR_CIRCUIT_NAME")] 70 | /// The name of the circuit to use in proof requests. 71 | /// Either "pi", "super", "evm", "state", "tx", "bytecode", "copy", "exp", "keccak" 72 | pub circuit_name: String, 73 | 74 | #[clap(long, env = "COORDINATOR_AGGREGATE_PROOF", default_value_t = false)] 75 | /// Signals the prover to aggregate the circuit proof 76 | pub aggregate_proof: bool, 77 | 78 | #[clap(long, env = "COORDINATOR_UNSAFE_RPC", default_value_t = false)] 79 | /// Allow unsafe rpc methods of the coordinator if true 80 | pub unsafe_rpc: bool, 81 | } 82 | 83 | impl Config { 84 | pub fn from_env() -> Self { 85 | Self::parse_from(std::env::args().skip(usize::MAX)) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /coordinator/src/faucet.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::sync::Arc; 3 | 4 | use ethers_core::types::Address; 5 | use ethers_core::types::U256; 6 | use ethers_signers::Signer; 7 | 8 | use tokio::spawn; 9 | use tokio::sync::Mutex; 10 | 11 | use crate::shared_state::SharedState; 12 | 13 | #[derive(Clone)] 14 | pub struct Faucet { 15 | pub queue: Arc>>, 16 | } 17 | 18 | impl Faucet { 19 | pub fn default() -> Faucet { 20 | Faucet { 21 | queue: Arc::new(Mutex::new(VecDeque::new())), 22 | } 23 | } 24 | 25 | /// Iterates over `queue` and sends ETH with the `shared_state.ro.l1_wallet`. 26 | /// To avoid replacing transactions or invoking other race conditions, 27 | /// this function should not be run in parallel with any other `SharedState` tasks. 28 | /// Only consumes up to `max_items` items from the queue each time. 29 | pub async fn drain(&self, shared_state: SharedState, max_items: usize) { 30 | let mut queue = self.queue.lock().await; 31 | let mut remaining_balance: U256 = shared_state 32 | .request_l1( 33 | "eth_getBalance", 34 | (shared_state.ro.l1_wallet.address(), "latest"), 35 | ) 36 | .await 37 | .expect("l1 balance"); 38 | 39 | // can be made configurable if needed 40 | let faucet_amount = U256::from(1000000000000000000u64); 41 | let min_wallet_balance = U256::from(1000000000000000000u64); 42 | 43 | let mut i = 0; 44 | for receiver in queue.iter().take(max_items) { 45 | log::info!("transfer of {} for {:?}", faucet_amount, receiver); 46 | 47 | if remaining_balance < faucet_amount { 48 | log::warn!( 49 | "remaining balance ({}) less than faucet amount ({})", 50 | remaining_balance, 51 | faucet_amount 52 | ); 53 | break; 54 | } 55 | if remaining_balance - faucet_amount < min_wallet_balance { 56 | log::warn!("faucet wallet balance is too low ({})", remaining_balance); 57 | break; 58 | } 59 | 60 | // spawn task to catch panics 61 | { 62 | #[allow(clippy::clone_on_copy)] 63 | let receiver = receiver.clone(); 64 | let shared_state = shared_state.clone(); 65 | let res = spawn(async move { 66 | shared_state 67 | .transaction_to_l1(Some(receiver), faucet_amount, vec![]) 68 | .await 69 | .expect("receipt"); 70 | }) 71 | .await; 72 | 73 | if let Err(err) = res { 74 | log::error!("drain: {}", err); 75 | break; 76 | } 77 | } 78 | 79 | remaining_balance -= faucet_amount; 80 | i += 1; 81 | } 82 | 83 | // drain all successful transfers 84 | queue.drain(0..i); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /coordinator/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod faucet; 3 | pub mod macros; 4 | pub mod shared_state; 5 | pub mod structs; 6 | pub mod utils; 7 | -------------------------------------------------------------------------------- /coordinator/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Wraps a expression inside an async block that timeouts after `a` ms 2 | #[macro_export] 3 | macro_rules! timeout { 4 | ($a:literal, $b:expr) => { 5 | async { 6 | let res = 7 | tokio::time::timeout(std::time::Duration::from_millis($a), async { $b }).await; 8 | 9 | if let Err(err) = &res { 10 | log::error!("timeout: {}", err); 11 | } 12 | res 13 | } 14 | .await 15 | .unwrap() 16 | }; 17 | } 18 | 19 | /// Returns `None` if env variable `a` is not set or disabled ("", "0" or "false"). 20 | /// Otherwise returns `Some(b)`. 21 | #[macro_export] 22 | macro_rules! option_enabled { 23 | ($a:literal, $b:expr) => { 24 | match var($a) { 25 | Err(_) => None, 26 | Ok(res) => match res.as_str() { 27 | "" => None, 28 | "0" => None, 29 | "false" => None, 30 | _ => Some($b), 31 | }, 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /coordinator/src/structs.rs: -------------------------------------------------------------------------------- 1 | use ethers_core::types::{Address, Bytes, H256, U256, U64}; 2 | use ethers_core::utils::keccak256; 3 | 4 | #[derive(Clone, Copy, serde::Serialize, serde::Deserialize)] 5 | pub struct ForkchoiceStateV1 { 6 | #[serde(rename = "headBlockHash")] 7 | pub head_block_hash: H256, 8 | #[serde(rename = "safeBlockHash")] 9 | pub safe_block_hash: H256, 10 | #[serde(rename = "finalizedBlockHash")] 11 | pub finalized_block_hash: H256, 12 | } 13 | 14 | #[derive(Clone, Debug)] 15 | pub struct MessageBeacon { 16 | pub id: H256, 17 | pub from: Address, 18 | pub to: Address, 19 | pub value: U256, 20 | pub fee: U256, 21 | pub deadline: U256, 22 | pub nonce: U256, 23 | pub calldata: Vec, 24 | } 25 | 26 | impl MessageBeacon { 27 | /// calculates the storage address for `self` 28 | pub fn storage_slot(&self) -> H256 { 29 | let mut buf: Vec = Vec::with_capacity(64); 30 | let sig = 0x31df76a4_u32.to_be_bytes(); 31 | 32 | buf.resize(28, 0); 33 | buf.extend(sig); 34 | buf.extend(self.id.as_ref()); 35 | 36 | keccak256(buf).into() 37 | } 38 | } 39 | 40 | #[derive(Debug, serde::Serialize)] 41 | pub struct SealBlockRequest<'a> { 42 | pub parent: &'a H256, 43 | pub random: &'a H256, 44 | pub timestamp: &'a U64, 45 | pub transactions: Option<&'a Vec>, 46 | } 47 | 48 | #[derive(Debug, serde::Deserialize)] 49 | pub struct BlockHeader { 50 | #[serde(rename = "parentHash")] 51 | pub parent_hash: H256, 52 | pub hash: H256, 53 | pub number: U64, 54 | #[serde(rename = "stateRoot")] 55 | pub state_root: H256, 56 | // add missing fields if required 57 | } 58 | 59 | // https://eips.ethereum.org/EIPS/eip-1186 60 | #[derive(Debug, serde::Deserialize)] 61 | pub struct ProofRequest { 62 | pub address: Address, 63 | #[serde(rename = "accountProof")] 64 | pub account_proof: Vec, 65 | pub balance: U256, 66 | #[serde(rename = "codeHash")] 67 | pub code_hash: H256, 68 | pub nonce: U256, 69 | #[serde(rename = "storageHash")] 70 | pub storage_hash: H256, 71 | #[serde(rename = "storageProof")] 72 | pub storage_proof: Vec, 73 | } 74 | 75 | #[derive(Debug, serde::Deserialize)] 76 | pub struct StorageProof { 77 | pub key: H256, 78 | pub value: U256, 79 | pub proof: Vec, 80 | } 81 | 82 | #[derive(Debug, Clone, serde::Deserialize)] 83 | pub struct Witness { 84 | pub randomness: U256, 85 | pub input: Bytes, 86 | } 87 | -------------------------------------------------------------------------------- /coordinator/tests/commitment/mod.rs: -------------------------------------------------------------------------------- 1 | use coordinator::shared_state::SharedState; 2 | use coordinator::structs::BlockHeader; 3 | use ethers_core::abi::Abi; 4 | use ethers_core::abi::AbiParser; 5 | use ethers_core::abi::Token; 6 | use ethers_core::abi::Tokenizable; 7 | use ethers_core::types::Bytes; 8 | use ethers_core::types::U256; 9 | use ethers_core::types::U64; 10 | use ethers_signers::Signer; 11 | use std::fs::File; 12 | use std::io::BufReader; 13 | use zkevm_common::prover::CircuitConfig; 14 | 15 | fn get_abi() -> Abi { 16 | AbiParser::default() 17 | .parse(&[ 18 | "function testPublicInputCommitment(uint256 MAX_TXS, uint256 MAX_CALLDATA, uint256 chainId, uint256 parentStateRoot, bytes calldata witness) returns (uint256[])", 19 | ]) 20 | .expect("parse abi") 21 | } 22 | 23 | pub(crate) async fn test_public_commitment( 24 | state: &SharedState, 25 | block_num: &U64, 26 | circuit_config: &CircuitConfig, 27 | ) -> Result, String> { 28 | let prev_block: BlockHeader = state 29 | .request_l2("eth_getHeaderByNumber", [block_num - 1]) 30 | .await 31 | .expect("prev_block"); 32 | let witness = state.request_witness(block_num).await.expect("witness"); 33 | let state_root_prev = U256::from(prev_block.state_root.as_ref()); 34 | let chain_id = state.ro.l2_wallet.chain_id(); 35 | let max_calldata = U256::from(circuit_config.max_calldata); 36 | let max_txs = U256::from(circuit_config.max_txs); 37 | 38 | let abi = get_abi(); 39 | let test_fn = abi.function("testPublicInputCommitment").unwrap(); 40 | let calldata = test_fn 41 | .encode_input(&[ 42 | max_txs.into_token(), 43 | max_calldata.into_token(), 44 | chain_id.into_token(), 45 | state_root_prev.into_token(), 46 | witness.input.into_token(), 47 | ]) 48 | .expect("calldata"); 49 | 50 | let path = "../build/contracts/combined.json"; 51 | let file = File::open(path).unwrap_or_else(|err| panic!("{}: {}", &path, err)); 52 | let reader = BufReader::new(file); 53 | let combined: serde_json::Value = serde_json::from_reader(reader).unwrap(); 54 | let bin_runtime = combined 55 | .get("contracts") 56 | .unwrap() 57 | .get("contracts/tests/ZkEvmTest.sol:ZkEvmTest") 58 | .unwrap() 59 | .get("bin-runtime") 60 | .unwrap(); 61 | 62 | let resp: Bytes = state 63 | .request_l1( 64 | "eth_call", 65 | serde_json::json!([ 66 | { 67 | "to": "0x00000000000000000000000000000000000f0000", 68 | "data": Bytes::from(calldata), 69 | }, 70 | "latest", 71 | { 72 | "0x00000000000000000000000000000000000f0000": { 73 | "code": format!("0x{}", bin_runtime.as_str().unwrap()), 74 | }, 75 | } 76 | ]), 77 | ) 78 | .await?; 79 | let mut result = test_fn.decode_output(resp.as_ref()).expect("decode output"); 80 | let table: Vec = result.pop().unwrap().into_array().unwrap(); 81 | let ret = table 82 | .iter() 83 | .map(|e| e.clone().into_uint().unwrap()) 84 | .collect(); 85 | 86 | Ok(ret) 87 | } 88 | -------------------------------------------------------------------------------- /coordinator/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use coordinator::shared_state::SharedState; 3 | use ethers_core::abi::decode; 4 | use ethers_core::abi::AbiParser; 5 | use ethers_core::abi::ParamType; 6 | use ethers_core::types::Bytes; 7 | use serde::de::IntoDeserializer; 8 | use serde::Deserialize; 9 | use std::collections::HashMap; 10 | use std::fs::File; 11 | use std::io::BufReader; 12 | use tokio::sync::Mutex; 13 | use tokio::sync::OnceCell; 14 | 15 | fn deserialize_bytes<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { 16 | let str = String::deserialize(deserializer).expect("String"); 17 | let val: serde_json::Value = format!("0x{}", str).into(); 18 | let res = Bytes::deserialize(val.into_deserializer()); 19 | 20 | Ok(res.unwrap()) 21 | } 22 | 23 | #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] 24 | pub struct CombinedJson { 25 | pub contracts: HashMap, 26 | } 27 | 28 | #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] 29 | pub struct ContractArtifact { 30 | #[serde(rename = "bin-runtime", deserialize_with = "deserialize_bytes")] 31 | pub bin_runtime: Bytes, 32 | pub bin: Bytes, 33 | } 34 | 35 | #[derive(Debug, serde::Deserialize)] 36 | pub struct Trace { 37 | pub gas: u64, 38 | #[serde(rename = "returnValue", deserialize_with = "deserialize_bytes")] 39 | pub return_value: Bytes, 40 | pub failed: bool, 41 | } 42 | 43 | impl ContractArtifact { 44 | pub fn load(name: &str) -> Self { 45 | let path = "../build/contracts/combined.json"; 46 | let file = File::open(path).unwrap_or_else(|err| panic!("{}: {}", &path, err)); 47 | let reader = BufReader::new(file); 48 | let combined: CombinedJson = serde_json::from_reader(reader).unwrap(); 49 | 50 | for (id, artifact) in combined.contracts { 51 | if id.ends_with(name) { 52 | return artifact; 53 | } 54 | } 55 | 56 | panic!("{} not found", name); 57 | } 58 | 59 | pub async fn l1_trace( 60 | &self, 61 | calldata: &Bytes, 62 | shared_state: &SharedState, 63 | ) -> Result { 64 | let req = serde_json::json!([ 65 | { 66 | "to": "0x00000000000000000000000000000000000f0000", 67 | "data": calldata, 68 | }, 69 | "latest", 70 | { 71 | "Limit": 1, 72 | "stateOverrides": { 73 | "0x00000000000000000000000000000000000f0000": { 74 | "code": self.bin_runtime, 75 | }, 76 | }, 77 | }, 78 | ]); 79 | let trace: serde_json::Value = shared_state 80 | .request_l1("debug_traceCall", &req) 81 | .await 82 | .expect("debug_traceCall"); 83 | let trace: Trace = serde_json::from_value(trace).unwrap(); 84 | if trace.failed { 85 | let revert_reason = decode(&[ParamType::String], &trace.return_value.as_ref()[4..]); 86 | if revert_reason.is_ok() { 87 | return Err(format!("{:?}", revert_reason)); 88 | } 89 | 90 | return Err("execution reverted".to_string()); 91 | } 92 | 93 | Ok(trace) 94 | } 95 | } 96 | 97 | pub fn proxy_abi() -> ethers_core::abi::Contract { 98 | AbiParser::default() 99 | .parse(&["function upgrade(address to) external"]) 100 | .unwrap() 101 | } 102 | 103 | pub fn zkevm_abi() -> ethers_core::abi::Contract { 104 | AbiParser::default() 105 | .parse(&[ 106 | // zkevm native bridge 107 | "function dispatchMessage(address to, uint256 fee, uint256 deadline, uint256 nonce, bytes calldata _data) external payable", 108 | ]) 109 | .expect("parse abi") 110 | } 111 | 112 | static ONCE: OnceCell> = OnceCell::const_new(); 113 | 114 | pub async fn get_shared_state() -> &'static Mutex { 115 | ONCE.get_or_init(|| async { 116 | let _ = 117 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")) 118 | .is_test(true) 119 | .try_init(); 120 | let shared_state = SharedState::from_env().await; 121 | shared_state.init().await; 122 | 123 | Mutex::new(shared_state) 124 | }) 125 | .await 126 | } 127 | 128 | #[macro_export] 129 | macro_rules! await_state { 130 | () => {{ 131 | get_shared_state().await.lock().await 132 | }}; 133 | } 134 | 135 | #[macro_export] 136 | macro_rules! sync { 137 | ($shared_state:expr) => { 138 | // sync bridge and process events 139 | $shared_state.sync().await; 140 | while $shared_state.rw.lock().await.l1_message_queue.len() > 0 { 141 | $shared_state.mine().await; 142 | $shared_state.sync().await; 143 | sleep!(300); 144 | } 145 | }; 146 | } 147 | 148 | #[macro_export] 149 | macro_rules! wait_for_tx { 150 | ($tx_hash:expr, $url:expr) => {{ 151 | let mut resp: Option = None; 152 | 153 | while (resp.is_none()) { 154 | resp = match jsonrpc_request($url, "eth_getTransactionReceipt", [$tx_hash]).await { 155 | Ok(val) => Some(val), 156 | Err(_) => None, 157 | }; 158 | } 159 | 160 | let receipt = resp.unwrap(); 161 | if receipt.status.unwrap() != U64::from(1) { 162 | panic!("transaction reverted"); 163 | } 164 | 165 | receipt 166 | }}; 167 | } 168 | 169 | #[macro_export] 170 | macro_rules! wait_for_tx_no_panic { 171 | ($tx_hash:expr, $url:expr) => {{ 172 | let mut resp: Option = None; 173 | 174 | while (resp.is_none()) { 175 | resp = match jsonrpc_request($url, "eth_getTransactionReceipt", [$tx_hash]).await { 176 | Ok(val) => Some(val), 177 | Err(_) => None, 178 | }; 179 | } 180 | 181 | let receipt = resp.unwrap(); 182 | receipt 183 | }}; 184 | } 185 | 186 | #[macro_export] 187 | macro_rules! finalize_chain { 188 | ($shared_state:expr) => { 189 | loop { 190 | let rw = $shared_state.rw.lock().await; 191 | if rw.chain_state.head_block_hash == rw.chain_state.finalized_block_hash { 192 | break; 193 | } 194 | drop(rw); 195 | 196 | sync!($shared_state); 197 | $shared_state.submit_blocks().await; 198 | $shared_state 199 | .finalize_blocks() 200 | .await 201 | .expect("finalize_blocks"); 202 | sync!($shared_state); 203 | while $shared_state.rw.lock().await.l2_message_queue.len() != 0 { 204 | $shared_state.relay_to_l1().await; 205 | sync!($shared_state); 206 | } 207 | } 208 | }; 209 | } 210 | 211 | #[macro_export] 212 | macro_rules! sleep { 213 | ($ms:expr) => {{ 214 | use tokio::time::{sleep, Duration}; 215 | sleep(Duration::from_millis($ms)).await; 216 | }}; 217 | } 218 | -------------------------------------------------------------------------------- /coordinator/tests/deploy.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::get_shared_state; 4 | use crate::common::proxy_abi; 5 | use crate::common::ContractArtifact; 6 | use ethers_core::abi::Tokenizable; 7 | use ethers_core::types::Address; 8 | use ethers_core::types::Bytes; 9 | use ethers_core::types::TransactionReceipt; 10 | use ethers_core::types::U256; 11 | use ethers_core::types::U64; 12 | use serde::Deserialize; 13 | use std::fs::read_dir; 14 | use std::fs::File; 15 | use std::io::BufReader; 16 | use std::str::FromStr; 17 | 18 | macro_rules! deploy_l1 { 19 | ($DEPLOY_CODE:expr, $ADDRESS:expr) => {{ 20 | let _ = 21 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")) 22 | .is_test(true) 23 | .try_init(); 24 | let abi = proxy_abi(); 25 | let shared_state = await_state!(); 26 | 27 | let receipt = shared_state 28 | .transaction_to_l1(None, U256::zero(), $DEPLOY_CODE) 29 | .await 30 | .expect("receipt"); 31 | assert!(receipt.status.unwrap() == U64::from(1)); 32 | 33 | let contract_addr = receipt.contract_address.expect("contract_address"); 34 | let calldata = abi 35 | .function("upgrade") 36 | .unwrap() 37 | .encode_input(&[contract_addr.into_token()]) 38 | .expect("calldata"); 39 | 40 | let receipt = shared_state 41 | .transaction_to_l1( 42 | Some(Address::from_str($ADDRESS).unwrap()), 43 | U256::zero(), 44 | calldata, 45 | ) 46 | .await 47 | .expect("receipt"); 48 | assert!(receipt.status.unwrap() == U64::from(1)); 49 | }}; 50 | } 51 | 52 | macro_rules! deploy_l2 { 53 | ($DEPLOY_CODE:expr, $ADDRESS:expr) => {{ 54 | let _ = 55 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")) 56 | .is_test(true) 57 | .try_init(); 58 | let abi = proxy_abi(); 59 | let shared_state = await_state!(); 60 | 61 | let tx_hash = shared_state 62 | .transaction_to_l2(None, U256::zero(), $DEPLOY_CODE, None) 63 | .await 64 | .expect("tx_hash"); 65 | shared_state.mine().await; 66 | 67 | let receipt: TransactionReceipt = shared_state 68 | .request_l2("eth_getTransactionReceipt", [tx_hash]) 69 | .await 70 | .expect("receipt"); 71 | assert!(receipt.status.unwrap() == U64::from(1)); 72 | 73 | let contract_addr = receipt.contract_address.expect("contract_address"); 74 | let calldata = abi 75 | .function("upgrade") 76 | .unwrap() 77 | .encode_input(&[contract_addr.into_token()]) 78 | .expect("calldata"); 79 | 80 | let tx_hash = shared_state 81 | .transaction_to_l2( 82 | Some(Address::from_str($ADDRESS).unwrap()), 83 | U256::zero(), 84 | calldata, 85 | None, 86 | ) 87 | .await 88 | .expect("tx_hash"); 89 | shared_state.mine().await; 90 | 91 | let receipt: TransactionReceipt = shared_state 92 | .request_l2("eth_getTransactionReceipt", [tx_hash]) 93 | .await 94 | .expect("receipt"); 95 | assert!(receipt.status.unwrap() == U64::from(1)); 96 | }}; 97 | } 98 | 99 | macro_rules! code { 100 | ($NAME:expr) => {{ 101 | ContractArtifact::load($NAME).bin.to_vec() 102 | }}; 103 | } 104 | 105 | #[tokio::test] 106 | async fn deploy_l1_bridge() { 107 | deploy_l1!( 108 | code!("ZkEvmL1Bridge"), 109 | "0x936a70c0b28532aa22240dce21f89a8399d6ac60" 110 | ); 111 | } 112 | 113 | #[tokio::test] 114 | async fn deploy_l1_optimism() { 115 | deploy_l1!( 116 | code!("L1OptimismBridge"), 117 | "0x936a70c0b28532aa22240dce21f89a8399d6ac61" 118 | ); 119 | } 120 | 121 | // TODO: l2 gas limit not sufficient 122 | #[ignore] 123 | #[tokio::test] 124 | async fn deploy_l2_bridge() { 125 | deploy_l2!( 126 | code!("ZkEvmL2MessageDeliverer"), 127 | "0x0000000000000000000000000000000000010000" 128 | ); 129 | deploy_l2!( 130 | code!("ZkEvmL2MessageDispatcher"), 131 | "0x0000000000000000000000000000000000020000" 132 | ); 133 | } 134 | 135 | // TODO: l2 gas limit not sufficient 136 | #[ignore] 137 | #[tokio::test] 138 | async fn deploy_l2_optimism() { 139 | deploy_l2!( 140 | code!("L2OptimisimBridge"), 141 | "0x4200000000000000000000000000000000000007" 142 | ); 143 | } 144 | 145 | #[tokio::test] 146 | async fn deploy_l1_evm_verifier() { 147 | #[derive(Deserialize)] 148 | struct Data { 149 | runtime_code: Bytes, 150 | address: String, 151 | } 152 | 153 | let items = read_dir("../build/plonk-verifier/"); 154 | if items.is_err() { 155 | return; 156 | } 157 | for item in items.unwrap() { 158 | let path = item.expect("path").path(); 159 | let file = File::open(&path).expect("open"); 160 | let data: Data = serde_json::from_reader(BufReader::new(file)).expect("json"); 161 | let mut deploy_code = vec![ 162 | 0x60, 0x0b, 0x38, 0x03, 0x80, 0x60, 0x0b, 0x3d, 0x39, 0x3d, 0xf3, 163 | ]; 164 | deploy_code.extend_from_slice(data.runtime_code.as_ref()); 165 | println!("{:?} {}", path, data.address); 166 | deploy_l1!(deploy_code, &data.address); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /coordinator/tests/misc.rs: -------------------------------------------------------------------------------- 1 | use coordinator::shared_state::SharedState; 2 | 3 | // test for: https://github.com/privacy-scaling-explorations/zkevm-chain/issues/5 4 | #[tokio::test] 5 | async fn access_list_regression() { 6 | let shared_state = SharedState::from_env().await; 7 | shared_state.init().await; 8 | 9 | // CODESIZE 10 | // CODESIZE 11 | // SLOAD 12 | // 13 | // CODESIZE 14 | // CODESIZE 15 | // SSTORE 16 | // 17 | // RETURNDATASIZE 18 | // CODESIZE 19 | // SSTORE 20 | // 21 | // CODESIZE 22 | // CODESIZE 23 | // SLOAD 24 | // 25 | // ADDRESS 26 | // EXTCODESIZE 27 | // 28 | // RETURNDATASIZE 29 | // NOT 30 | // EXTCODESIZE 31 | // 32 | // CALLVALUE 33 | // EXTCODEHASH 34 | // 35 | // RETURNDATASIZE 36 | // RETURNDATASIZE 37 | // RETURNDATASIZE 38 | // RETURNDATASIZE 39 | // CODESIZE 40 | // CALLVALUE 41 | // GAS 42 | // CALL 43 | let req = serde_json::json!([ 44 | { 45 | "data": "0x3838543838553d3855383854303b3d193b343f3d3d3d3d38345af1", 46 | "value": "0xfafbfc", 47 | "gas": "0x2faf080", 48 | }, 49 | "latest", 50 | { 51 | "stateOverrides": { 52 | "0x0000000000000000000000000000000000000000": { 53 | "balance": "0xffffffff", 54 | }, 55 | }, 56 | }, 57 | ]); 58 | let l2: serde_json::Value = shared_state 59 | .request_l2("debug_traceCall", &req) 60 | .await 61 | .expect("should not crash"); 62 | let l1: serde_json::Value = shared_state 63 | .request_l1("debug_traceCall", &req) 64 | .await 65 | .expect("should not crash"); 66 | 67 | assert_eq!(l1, l2, "trace should be equal"); 68 | } 69 | -------------------------------------------------------------------------------- /coordinator/tests/patricia.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::ContractArtifact; 4 | use coordinator::shared_state::SharedState; 5 | use coordinator::structs::ProofRequest; 6 | use coordinator::utils::marshal_proof; 7 | use ethers_core::abi::AbiParser; 8 | use ethers_core::abi::Tokenizable; 9 | use ethers_core::types::Bytes; 10 | use ethers_core::types::H256; 11 | use ethers_core::types::U256; 12 | use std::fs::File; 13 | use std::io::BufReader; 14 | 15 | #[derive(Debug, serde::Deserialize)] 16 | struct BlockHeader { 17 | #[serde(rename = "stateRoot")] 18 | state_root: H256, 19 | } 20 | 21 | #[derive(Debug, serde::Deserialize)] 22 | struct TestData { 23 | block: BlockHeader, 24 | proof: ProofRequest, 25 | } 26 | 27 | #[tokio::test] 28 | async fn patricia_validator() { 29 | let abi = AbiParser::default() 30 | .parse(&[ 31 | "function testPatricia(address account, bytes32 storageKey, bytes calldata proofData) external returns (bytes32 stateRoot, bytes32 storageValue)", 32 | ]) 33 | .expect("parse abi"); 34 | 35 | let shared_state = SharedState::from_env().await; 36 | shared_state.init().await; 37 | 38 | let mut cumulative_gas = 0; 39 | let mut samples = 0; 40 | for entry in std::fs::read_dir("tests/patricia/").unwrap() { 41 | let path = entry.expect("path").path(); 42 | let file = File::open(&path).expect("file"); 43 | let reader = BufReader::new(file); 44 | let test_data: TestData = serde_json::from_reader(reader).expect("json"); 45 | let block_header = test_data.block; 46 | let proof = test_data.proof; 47 | let account = proof.address; 48 | 49 | for storage_proof in proof.storage_proof { 50 | let storage_key = storage_proof.key; 51 | let proof_data: Bytes = 52 | Bytes::from(marshal_proof(&proof.account_proof, &storage_proof.proof)); 53 | let calldata = abi 54 | .function("testPatricia") 55 | .unwrap() 56 | .encode_input(&[ 57 | account.into_token(), 58 | storage_key.into_token(), 59 | proof_data.into_token(), 60 | ]) 61 | .expect("calldata"); 62 | 63 | let result = ContractArtifact::load("ZkEvmTest") 64 | .l1_trace(&Bytes::from(calldata), &shared_state) 65 | .await; 66 | let error_expected = storage_proof.value.is_zero(); 67 | assert_eq!( 68 | result.is_err(), 69 | error_expected, 70 | "{:?} {:?} {:?}", 71 | result, 72 | storage_proof, 73 | path 74 | ); 75 | if !error_expected { 76 | let trace = result.unwrap(); 77 | let mut res = abi 78 | .function("testPatricia") 79 | .unwrap() 80 | .decode_output(trace.return_value.as_ref()) 81 | .expect("decode output"); 82 | let storage_value = H256::from_token(res.pop().unwrap()).expect("bytes"); 83 | let state_root = H256::from_token(res.pop().unwrap()).expect("bytes"); 84 | 85 | assert_eq!(state_root, block_header.state_root, "state_root"); 86 | assert_eq!( 87 | U256::from(storage_value.as_ref()), 88 | storage_proof.value, 89 | "storage_value" 90 | ); 91 | 92 | // remove 'tx' cost 93 | cumulative_gas += trace.gas - 21_000; 94 | samples += 1; 95 | } 96 | } 97 | } 98 | 99 | let avg: u64 = cumulative_gas / samples; 100 | log::info!( 101 | "patricia_cumulative_gas={} samples={} avg={}", 102 | cumulative_gas, 103 | samples, 104 | avg 105 | ); 106 | 107 | const MAX_DIFF: u64 = 1000; 108 | const KNOWN_AVG: u64 = 63028; 109 | if !((KNOWN_AVG - MAX_DIFF)..=(KNOWN_AVG + MAX_DIFF)).contains(&avg) { 110 | panic!( 111 | "patricia_validator: please update KNOWN_AVG ({}), new value: {}", 112 | KNOWN_AVG, avg 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /coordinator/tests/patricia/test-data-68b3465833fb72a70ecdf485e0e4c7bd8665fc45.json: -------------------------------------------------------------------------------- 1 | { 2 | "block": { 3 | "hash": "0x2e06ab9550d18475bc1960be5fb4e0feab74bff64fa4f285811fdc20be93481e", 4 | "number": "0xdd603d", 5 | "stateRoot": "0xa97fd4fde8b51449e2f7b3650346e41512c8c489c5e1e6b732ee70548364efeb" 6 | }, 7 | "proof": { 8 | "address": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", 9 | "accountProof": [ 10 | "0xf90211a078d28a611634f05a4a2db440db921a6c9b4cb46dd669bfb60d3949f1e6b60434a00dbce70ef3e475cc1b65a863af013827e59de99eff7a5d34b0875aeabb78e004a0f4962914fc43bda6c00a8602c873f73078005052b87b9451d9e81a62201c97e7a074b5be993053bf59d706374c5ec20bbb2a94fd670f8db115652fc009041c2f7ca0b5a2f43adcb95e6ae92f14fe6bb21842089fd2760ed3a5246fbdd189051a7b29a020d2c934926131b7f577af92e82f718d71f1595abc2db803c8c0077dca371172a0c5b7780da3baf666888990bea6bca4dbb4b5fb71adc37c9e06ce918e6583b497a0e88121f2cd39b8d74a435152679aaa07a0795343970d070bcaa894654a4eaeb8a07e1bcde37841842f92db5f1d2fdcdbb83ccc278856345b8b4ce9c18ffebddbe8a0dfaecdb2d1628d2a2b37d9c106ff10904f817301b5113b90ebbc1349c0c85995a09d479c082dc2438679df42e6bee61b1a15859fe377eec912497fb67fb75fb8d8a04b8457bfbe38e5467ea0c9d368c7cc33a3c268e5c696901e9a9c85c72bd28c09a08d5c7ebbf0b69ac6c0be38d17fd4ef538d861ac1b98fa18b61597d4669c92703a0d0dc67d453ec0007e58fad085862827139694d4f958e9d070d7d87ea20a111f8a035f016532ea5f19d0a1f7de89e7bb49ec162b21ffe6063b9c3f150083442e46da02faadee66576394af647b859362ff2aa3a8d197ff5c42ab5e4ba4ac088a56cf280", 11 | "0xf90211a0f4c9f2525f43077f7d86ebb57c1399ea983a670054248a8a603fc29a169e809ba01ae15d8ae74b23647605bf91538ebb572fafd79c33cc82c036455a093ae55a73a0a4333294109e8fab4ad287c4d5462611c8558ed24817855c43499f0106f38230a072dcabd12be05c93ac1cc05245a1d62b2d879d6c4098844cfa59b047a4eb8cdfa0072ab91331f5aec2dea7649bbd7e049e401c80d9778897999cd2f2818000c8e9a0a491804ffdf291c5541987d61fec25cb06634d16f027cc4f7c2b6ac87ae4625da00d05a8fa8b7dc01e60a7f9a730feac07fbc56e9796b9cdf62867a9be7752e076a06b3228a09126486405fd19a20090a78312e6ebd11244adbd7e476bdefb4b9a75a03722ce4aa8f4e9a009d88a31ae9c401292499a4b9cd216b7f205972e8744cc0ba0f90bf90a5aae3f1c0f7a30ee8459c7f22db6f489728f0bcb9992097477a03831a0b91c669dae336b1ef5bef137ace62a02295cb85fb5a923eaee3f638c7fc53381a063849ad20a7c705d01704034e1a88ffc46fa42a6484def0a9e1bc4c8f871a5cea02ec3b92ee5bc56212c8cb38ecce8b5d1dcd1b7066fa05afd74dd6895c6fe6121a003e0fe220f673180d46a8be21f60e6219a20906025b110c69bb19bc18234b5a7a075936e51066c4a4da82309fd4cc45dc04a2f242218a9060589ab9e15938738e0a0288cf618c91b458f4593f3a6b87945c90e39ce07a353a8b7e8abdb9a8006805f80", 12 | "0xf90211a0e2b99413bcfd6c430c1d3d0c5ee3efe04cab172d20bf78a8533c90e30f7f024ca06710d505fd81f61d51ba8bb0ecff3f6e67b5d61a0a584d800d4781fcb5882ac8a02c8b9e73752edd51943d3ec178d4ab6a15d4942c44619474237516031d02c61aa08fa3995513df73b4c87abdb284ef4702520d0321688282aff4c85ca19b00324ca0873884c4d3feaeb02b74aea25f735065a1a0342b676d39f4056530c736cd89c5a049832c4adc01a652fdb4a6f0688f30a01cc4ce7dc278f007460c82c6d4c36ff7a05cb1fb4341059eeab457e0b12a680e718ea2fb52dc996e4c1ecea3384165a009a0f79801e32dfe2f3015da3ac58f71580c50142d72f317e33a0c2205783166a494a033b5ddc2fb3ce79d8eae30d7914c07bd8820e0e1045e41cd33851199804b5f61a087ff7b561744ce17764edc97bf7ee4178c15462f412b33efb6c5770d8c14535ba0d530690600f6574ccae37cc6a64f34b6538959bca894d4be883939a509fa0e09a079a75ab5d8ef344c3be262dfb2c499552eabd0edf639b53aaf8b96f1bf88fedaa096c21f84562ab8daa1f5a1e12978e0c4d9c03735539aaacaa44085f80b5a7963a0d913ac2ae87bdb058581ff33bcc118ded9204541fdf94a29dad4b1b84d92aba3a09d7af235f5d85c25c866d0739afb38e1c572da6efa5fdd53ed26340ab3ec0adaa0d37c68224da4098a5a0e671a5e23f704909e3b9feb1567d9a0c37e19d45b6d4d80", 13 | "0xf90211a0171a2b386d318f530c3df1a756bcc8977d30db8e93bca429dd4876410ad2c969a0e4c89231ea08f991a3fe037b2adac4f068bcf096c9c936ee04f4297466c62c82a0e0a9f272ce653f57ac163cb68d0c9b76f8ec58df6e649f9ba04d0cbc4e3ffad0a09f1ed381bc2ef94cbf8f55485807b55b3b9e8e9ff13039fc4e566d25d387b125a0b71c5fbf7f8e56d2d8838a4b66761389c510eeb360134a717bf761c8a8f2e02da070b9e6b723df4d5e24312469bb1a9b4da6f2afae88bca9b5de8dd0cceb688653a0de1177f4df3b781df91f43f581f94e27b61781789264e4c83ed5f1a80524a2d1a0c4880758fcc29e0b474a1c3479896d72fb5bd4559262aedae54b9d8747dae191a0a1800f4f339e561aa4064462fd0edea52912720e6b5ee4f48a182717590f22cba024187118f5e631901a39bc74be6968b6616a6310013f8e0cbaa8bc59dde7610da0a8f89508ff4f0b66a07c645c682b34d2d4f1267e76db8c59762434e243b502e1a0851c5cb94aae99946a1e69fc62ff2e149558277ea5c34af59fd2b97842f384aaa00f93c5b560f86303d1b26b5a18c7050553f4e2d3345f52e660a0ccad4cf89c7da05598b3e6b2e45007660d38379112dcca5d3f2016ec683c06fa25470f7c24aa4aa0cc63f9285ac72c6736b4b0336954eb07c6d98adaecc77db1b600e336fa486881a025a060945fa852def7fa313e4e17a569e170bbfb585ebca491aef6b584b2ae0180", 14 | "0xf90211a0d3d818ddb3d22547ccd2eddea6f18cb61a886852d53079d373db435199f640c2a08817b02478cdc8e5106aafd20394f8887d15fa3ff5054a8a4272c648552990a9a08f3cb88dff2c42b517a26f174300cef18d1db93b2b503aeec69627730d466423a0223fd24817583f4eec1eed2bcb8bab16aa32ee61576c745a55bb96a8bbe9add4a0f6aa1c419f4133e52db4f4d86695958b68c1032e503df9f4a42858061aeb5097a029f255fe929fe835a78c356117c373fe02e571e366dcbc17c5846161683909d1a04ce5a56c928d7d0fff0dca0b1f6e5fece54f375309e4814c8eadac8be76d46a5a04ef9680ec5349d695c9a19affab6c070e95258cb0582503834f11a7e2a9c0191a009b7f2a930381077afa1eb4c5e670dac97252b9ac2d9fb203ee9bbf0f902c356a0a4f6c74f7ccaec0aa89515100ae20bc187e7ad1a4d7903577e84d59f4c125b6ba0b44377babeb18cdbb4f660cf75eaafeb1ba107d78a9d2098ed94d9237ffa819aa02b358f8439c66371769a22ad96af573043c011735eb0f384f5036cedc1b2698aa00d6ead2a7bf26ac81a0ee8d3969857982a4cb12030a3777ed7f6cc655b704108a06fc8eaa9973bfb793b841241c4cc5abdcf1ef1c825491bf644697c93b45fc8efa000dbca146c594e39ac7f9b74632a8b0315568153c371b78e55d44b4f5c504952a0c87eaf0a3cad90100b1bd655ef847e647fedfa4180b5875da84f31cd8db7b73180", 15 | "0xf90211a030150d5144c449c9baf20b9190ecd03373cf75c1e9831843c45133e4031e485ca00ef9aac4fe6dc4e486ee49dae494b247eb2d829c04e4bed1bd333f6af33999f1a05a22c84a551c933006b5489e31666904d17ee1fcc6a96d1bfc4dd1ed07b461f9a0bc1f43358a9045e2b1f15b5ecb9b6b753cb6f4a4f21202e58a0963222b941074a071903392a4b1fd43c42791b75c232b3bcb79a2c4bf213dcef263ac328b8dfb12a085e32de14238466d2831c93eed27b550ee7dcf5ea5b0f3b7bb2690aaf3ffd1bba0bae11e159d1bbb728dbbe11fc417ad26bdc84b29678e3273d18807f8c5d631d5a06711622e42be4146c38b36df1031ed659170a22df28733a88327fad6ae9c7412a074c538aeab707bd45ef692586750ef22f09870b9770eab0ef3ef99e93ac24f0da057fd50596f086136fa6af90960b8ca9f9eb5e93f4e3ea9c0d48c584bde2522cda0b29f36b18fcf14dfa827e044bbb33089e2e49dc0bf8e3b16d00dcc50dd37823ba0232089b885f024cffb338d18c3093e3ab39215fbd3a4cc70e5c8f59c42f2745da0c1f349440a107b841f95b7f90eb6be373ad5d8bcfc505b70f5e9b5056096ba11a0f624577168c9a273e73b870df278a14aa64731d6c88d0dfd71f8fac8bc91f250a0ad3eef677c5178605e3f79db12b40eb8d32fa4abd956c9aa5368f8fc62061ebca08cd4aabe820e0f1a4c9a2f392cecaad5b70d898c090189a7eae2e55dfed0e64980", 16 | "0xf8b180a0ab020129cdb0cfed67ec9a8356fb8221b4cd6155d214a7c8f5a917001e09ad5a80a099c45c51bcd00b5392b7c11f5b1b0e58aef2a73c6dec59b92efa3bb81fd5e598808080a0912873d1cb040773f8ea4fdfd47c5878865018146ddb83f0a4d1f9c5707697d18080a06ba36cdc9f472e3bfa0b4cff756393a43af1144d2fe01a13647c94ffce1e5ad080808080a03bbc3b3a41baf53302e2037c946b918c31643bef7bdcba323677aa5a701315dd80", 17 | "0xf871808080808080a05a1dee9a276a9fa4c04f7996602f2ad10a0a2d90e61db1a9d947b90e975877a8808080a045124fc2935641e85b0fca8b4386b0b5a62ade7ce4010a70ce5c29505ec14ff280808080a03ca4718f7f70123487e56aa2e5242aca37821212e16a7218766ed22e45d67c5b80", 18 | "0xf8669d208d89b54e324fc790acf237c77d2b1a3c111059ce53b4bf66e98e4663b846f8440180a0ca6f0fbdeda818216f399c395dc814121e66bca0139cef25a2b81223c438c1f6a06ec798e80f3a19de650826338677604e54d6664f44a33b53a20b22b1939f402e" 19 | ], 20 | "balance": "0x0", 21 | "codeHash": "0x6ec798e80f3a19de650826338677604e54d6664f44a33b53a20b22b1939f402e", 22 | "nonce": "0x1", 23 | "storageHash": "0xca6f0fbdeda818216f399c395dc814121e66bca0139cef25a2b81223c438c1f6", 24 | "storageProof": [ 25 | { 26 | "key": "0x0000000000000000000000000000000000000000000000000000000000000000", 27 | "value": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 28 | "proof": [ 29 | "0xf844a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 30 | ] 31 | }, 32 | { 33 | "key": "0x7da691afe6811309755d64b08db18bdc3b559237c4cc2d886875c65d6c3c9e3f", 34 | "value": "0x0", 35 | "proof": [ 36 | "0xf844a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 37 | ] 38 | } 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /coordinator/tests/verifier.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::ContractArtifact; 4 | use coordinator::shared_state::SharedState; 5 | use coordinator::utils::*; 6 | use ethers_core::abi::AbiParser; 7 | use ethers_core::abi::Token; 8 | use ethers_core::abi::Tokenizable; 9 | use ethers_core::types::Block; 10 | use ethers_core::types::Bytes; 11 | use ethers_core::types::Transaction; 12 | use ethers_core::types::H256; 13 | use ethers_core::types::U256; 14 | use lzma::LzmaReader; 15 | use std::fs::File; 16 | 17 | const COLUMNS: [&str; 12] = [ 18 | "q_block_table", 19 | "block_table", 20 | "q_tx_table", 21 | "tx_table.id", 22 | "tx_table.tag", 23 | "tx_table.index", 24 | "tx_table.value", 25 | "raw_public_inputs", 26 | "rpi_rlc_acc", 27 | "rand_rpi", 28 | "q_end", 29 | "q_not_end", 30 | ]; 31 | 32 | #[derive(Debug, serde::Deserialize)] 33 | struct PublicInputs { 34 | max_txs: U256, 35 | max_calldata: U256, 36 | // rand_rpi: U256, 37 | chain_id: U256, 38 | // rpi_rlc: U256, 39 | // state_root: U256, 40 | state_root_prev: U256, 41 | } 42 | 43 | #[derive(Debug, serde::Deserialize)] 44 | struct TestData { 45 | block: Block, 46 | block_hashes: Vec, 47 | rows: Vec>, 48 | public_inputs: PublicInputs, 49 | } 50 | 51 | #[tokio::test] 52 | async fn witness_verifier() { 53 | let abi = AbiParser::default() 54 | .parse(&[ 55 | "function Error(string)", 56 | "function testPublicInput(uint256 MAX_TXS, uint256 MAX_CALLDATA, uint256 chainId, uint256 parentStateRoot, bytes calldata witness) external returns (uint256[])", 57 | ]) 58 | .expect("parse abi"); 59 | let shared_state = SharedState::from_env().await; 60 | shared_state.init().await; 61 | 62 | for entry in std::fs::read_dir("tests/verifier/").unwrap() { 63 | let path = entry.expect("path").path(); 64 | let file = File::open(&path).expect("file"); 65 | let reader = LzmaReader::new_decompressor(file).unwrap(); 66 | let test_data: TestData = serde_json::from_reader(reader).expect("json"); 67 | 68 | let witness: Bytes = encode_verifier_witness( 69 | &test_data.block, 70 | test_data.block_hashes.as_slice(), 71 | &test_data.public_inputs.chain_id.as_u64(), 72 | ) 73 | .expect("encode_verifier_witness") 74 | .into(); 75 | 76 | let calldata = abi 77 | .function("testPublicInput") 78 | .unwrap() 79 | .encode_input(&[ 80 | test_data.public_inputs.max_txs.into_token(), 81 | test_data.public_inputs.max_calldata.into_token(), 82 | test_data.public_inputs.chain_id.into_token(), 83 | test_data.public_inputs.state_root_prev.into_token(), 84 | witness.into_token(), 85 | ]) 86 | .expect("calldata"); 87 | 88 | println!("{:?}", path); 89 | 90 | let trace = ContractArtifact::load("ZkEvmTest") 91 | .l1_trace(&Bytes::from(calldata), &shared_state) 92 | .await 93 | .unwrap(); 94 | let mut result = abi 95 | .function("testPublicInput") 96 | .unwrap() 97 | .decode_output(trace.return_value.as_ref()) 98 | .expect("decode output"); 99 | let table: Vec = result.pop().unwrap().into_array().unwrap(); 100 | 101 | assert_eq!(test_data.rows.len() * COLUMNS.len(), table.len(), "# rows"); 102 | 103 | let mut success = true; 104 | for (i, token) in table.iter().enumerate() { 105 | let tag = COLUMNS[i % COLUMNS.len()]; 106 | let value: U256 = token.clone().into_uint().unwrap(); 107 | if i % COLUMNS.len() == 0 { 108 | // start of new row 109 | println!("row({})", i / COLUMNS.len()); 110 | } 111 | println!("{:4}({:17})={:064x}", i, tag, value); 112 | 113 | let row = &test_data.rows[i / COLUMNS.len()]; 114 | let expected = row[i % COLUMNS.len()]; 115 | 116 | if std::env::args().any(|e| e == "--nocapture") { 117 | if expected != value { 118 | success = false; 119 | println!( 120 | "{:?}:{} expected={:064x} has={:064x}", 121 | path, tag, expected, value 122 | ); 123 | } 124 | } else { 125 | assert_eq!( 126 | expected, value, 127 | "{:?}:{} expected={:064x} has={:064x}", 128 | path, tag, expected, value 129 | ); 130 | } 131 | } 132 | 133 | println!("{:?}: gas={}", path, trace.gas); 134 | assert!(success); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /coordinator/tests/verifier/0-0.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smtmfft/zkevm-chain/6924bd50eb2d65b60f3bfa79d1263384680a1e46/coordinator/tests/verifier/0-0.json.xz -------------------------------------------------------------------------------- /coordinator/tests/verifier/1-5.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smtmfft/zkevm-chain/6924bd50eb2d65b60f3bfa79d1263384680a1e46/coordinator/tests/verifier/1-5.json.xz -------------------------------------------------------------------------------- /coordinator/tests/verifier/2-1.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smtmfft/zkevm-chain/6924bd50eb2d65b60f3bfa79d1263384680a1e46/coordinator/tests/verifier/2-1.json.xz -------------------------------------------------------------------------------- /coordinator/tests/verifier/3-3072.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smtmfft/zkevm-chain/6924bd50eb2d65b60f3bfa79d1263384680a1e46/coordinator/tests/verifier/3-3072.json.xz -------------------------------------------------------------------------------- /coordinator/tests/verifier/4-1024.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smtmfft/zkevm-chain/6924bd50eb2d65b60f3bfa79d1263384680a1e46/coordinator/tests/verifier/4-1024.json.xz -------------------------------------------------------------------------------- /coordinator/tests/worst_case.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::get_shared_state; 4 | use ethers_core::types::Address; 5 | use ethers_core::types::Block; 6 | use ethers_core::types::TransactionReceipt; 7 | use ethers_core::types::H256; 8 | use ethers_core::types::U256; 9 | use std::str::FromStr; 10 | use zkevm_common::json_rpc::jsonrpc_request; 11 | 12 | #[tokio::test] 13 | async fn worst_case_smod() { 14 | let shared_state = await_state!(); 15 | let latest_block: Block = shared_state 16 | .request_l2("eth_getBlockByNumber", ("latest", false)) 17 | .await 18 | .unwrap(); 19 | let block_gas_limit = latest_block.gas_limit; 20 | let tx_hash = shared_state 21 | .transaction_to_l2( 22 | Some(Address::from_str("0x0000000000000000000000000000000000100001").unwrap()), 23 | U256::zero(), 24 | vec![], 25 | Some(block_gas_limit), 26 | ) 27 | .await 28 | .expect("tx_hash"); 29 | shared_state.mine().await; 30 | let receipt = wait_for_tx_no_panic!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 31 | assert_eq!(receipt.gas_used.expect("gas_used"), block_gas_limit); 32 | finalize_chain!(shared_state); 33 | } 34 | 35 | #[tokio::test] 36 | async fn worst_case_mload() { 37 | let shared_state = await_state!(); 38 | let latest_block: Block = shared_state 39 | .request_l2("eth_getBlockByNumber", ("latest", false)) 40 | .await 41 | .unwrap(); 42 | let block_gas_limit = latest_block.gas_limit; 43 | let tx_hash = shared_state 44 | .transaction_to_l2( 45 | Some(Address::from_str("0x0000000000000000000000000000000000100002").unwrap()), 46 | U256::zero(), 47 | vec![], 48 | Some(block_gas_limit), 49 | ) 50 | .await 51 | .expect("tx_hash"); 52 | shared_state.mine().await; 53 | let receipt = wait_for_tx_no_panic!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 54 | assert_eq!(receipt.gas_used.expect("gas_used"), block_gas_limit); 55 | finalize_chain!(shared_state); 56 | } 57 | 58 | #[tokio::test] 59 | async fn worst_case_keccak_0_32() { 60 | let shared_state = await_state!(); 61 | let latest_block: Block = shared_state 62 | .request_l2("eth_getBlockByNumber", ("latest", false)) 63 | .await 64 | .unwrap(); 65 | let block_gas_limit = latest_block.gas_limit; 66 | let tx_hash = shared_state 67 | .transaction_to_l2( 68 | Some(Address::from_str("0x0000000000000000000000000000000000100003").unwrap()), 69 | U256::zero(), 70 | vec![], 71 | Some(block_gas_limit), 72 | ) 73 | .await 74 | .expect("tx_hash"); 75 | shared_state.mine().await; 76 | let receipt = wait_for_tx_no_panic!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 77 | assert_eq!(receipt.gas_used.expect("gas_used"), block_gas_limit); 78 | finalize_chain!(shared_state); 79 | } 80 | -------------------------------------------------------------------------------- /dev/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkevm_dev" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | eth-types = { git = "https://github.com/pinkiebell/zkevm-circuits.git", branch = "zkevm-chain" } 9 | serde = { version = "1.0.136", features = ["derive"] } 10 | serde_json = { version = "1.0.78", features = ["preserve_order"] } 11 | 12 | [features] 13 | default = [] 14 | autogen = [] 15 | -------------------------------------------------------------------------------- /dev/src/bytecode.rs: -------------------------------------------------------------------------------- 1 | use crate::genesis::get_max_contract_size; 2 | 3 | #[macro_export] 4 | macro_rules! bytecode_repeat { 5 | ($code:ident, $repeat:expr, $($args:tt)*) => {{ 6 | for _ in 0..$repeat { 7 | eth_types::bytecode_internal!($code, $($args)*); 8 | } 9 | }}; 10 | 11 | ($({$repeat:expr, $($args:tt)*},)*) => {{ 12 | let mut code = eth_types::bytecode::Bytecode::default(); 13 | 14 | $( 15 | bytecode_repeat!(code, $repeat, $($args)*); 16 | )* 17 | 18 | code 19 | }}; 20 | } 21 | 22 | pub fn gen_bytecode_smod(gas_limit: usize) -> eth_types::Bytecode { 23 | let max_contract_size = get_max_contract_size(gas_limit); 24 | let fixed_bytes = 5; 25 | let iteration_size = 2; 26 | let iterations = (max_contract_size - fixed_bytes) / iteration_size; 27 | let loop_offset: usize = 1; 28 | bytecode_repeat!( 29 | // prelude 30 | { 31 | 1, 32 | GAS // gas=2 33 | JUMPDEST // gas=1 34 | }, 35 | // chain SMOD(gas, previous value) 36 | { 37 | iterations, 38 | GAS // gas=2 39 | SMOD // gas=5 40 | }, 41 | // loop with remaining gas 42 | { 43 | 1, 44 | PUSH1(loop_offset) // gas=3 45 | JUMP // gas=8 46 | }, 47 | ) 48 | } 49 | 50 | pub fn gen_bytecode_mload(gas_limit: usize) -> eth_types::Bytecode { 51 | let max_contract_size = get_max_contract_size(gas_limit); 52 | let fixed_bytes = 5; 53 | let iterations = max_contract_size - fixed_bytes; 54 | let loop_offset: usize = 1; 55 | bytecode_repeat!( 56 | // prelude 57 | { 58 | 1, 59 | CALLDATASIZE // gas=2 60 | JUMPDEST // gas=1 61 | }, 62 | // chain mload 63 | { 64 | iterations, 65 | MLOAD // gas=3 66 | }, 67 | { 68 | 1, 69 | PUSH1(loop_offset) // gas=3 70 | JUMP // gas=8 71 | }, 72 | ) 73 | } 74 | 75 | pub fn gen_bytecode_keccak_0_32(gas_limit: usize) -> eth_types::Bytecode { 76 | let max_contract_size = get_max_contract_size(gas_limit); 77 | let fixed_bytes = 6; 78 | let iteration_size = 4; 79 | let iterations = (max_contract_size - fixed_bytes) / iteration_size; 80 | let loop_offset: usize = 2; 81 | bytecode_repeat!( 82 | { 83 | 1, 84 | PUSH1(32) // gas=3 85 | JUMPDEST // gas=1 86 | }, 87 | { 88 | iterations, 89 | DUP1 // gas=3 90 | RETURNDATASIZE // gas=2 91 | SHA3 // gas=30 + 6 + (memory expansion once) 92 | POP // gas=2 93 | }, 94 | { 95 | 1, 96 | PUSH1(loop_offset) // gas=3 97 | JUMP // gas=8 98 | }, 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /dev/src/genesis.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Bytes; 2 | use serde_json::json; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | 6 | pub fn patch_genesis_l2(name: &str, address: usize, bytecode: Bytes) { 7 | let path = "../docker/geth/templates/l2-testnet.json"; 8 | let file = File::open(path).unwrap_or_else(|err| panic!("{}: {}", &path, err)); 9 | let reader = BufReader::new(&file); 10 | let mut genesis: serde_json::Value = serde_json::from_reader(reader).unwrap(); 11 | let addr = format!("{:040x}", address); 12 | genesis["alloc"][addr] = json!({ 13 | "comment": name, 14 | "balance": "0", 15 | "code": bytecode.to_string(), 16 | }); 17 | serde_json::to_writer_pretty(File::create(path).unwrap(), &genesis).expect("write"); 18 | } 19 | 20 | pub fn get_max_contract_size(gas_limit: usize) -> usize { 21 | let max_deploy_opcodes = (gas_limit - 32_000) / 16; 22 | std::cmp::max(24_576, max_deploy_opcodes) 23 | } 24 | -------------------------------------------------------------------------------- /dev/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bytecode; 2 | pub mod genesis; 3 | -------------------------------------------------------------------------------- /dev/tests/autogen.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "autogen")] 2 | 3 | use eth_types::Bytes; 4 | use zkevm_dev::bytecode::*; 5 | use zkevm_dev::genesis::patch_genesis_l2; 6 | 7 | #[test] 8 | fn autogen_genesis() { 9 | patch_genesis_l2( 10 | "worst-case smod", 11 | 0x100001, 12 | Bytes::from(gen_bytecode_smod(300_000)), 13 | ); 14 | 15 | patch_genesis_l2( 16 | "worst-case mload", 17 | 0x100002, 18 | Bytes::from(gen_bytecode_mload(300_000)), 19 | ); 20 | 21 | patch_genesis_l2( 22 | "worst-case keccak_0_32", 23 | 0x100003, 24 | Bytes::from(gen_bytecode_keccak_0_32(300_000)), 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /docker-compose-perf.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | dev: 5 | cap_add: 6 | - SYS_ADMIN 7 | -------------------------------------------------------------------------------- /docker-compose-pub.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | networks: 4 | default: 5 | name: zkevm-chain 6 | 7 | services: 8 | leader-testnet-geth: 9 | restart: unless-stopped 10 | 11 | server-testnet-geth: 12 | restart: unless-stopped 13 | 14 | l1-testnet-geth: 15 | restart: unless-stopped 16 | 17 | bootnode: 18 | restart: unless-stopped 19 | ports: 20 | - 30303:30303 21 | 22 | dev: 23 | ports: 24 | - 8000:8000 25 | 26 | coordinator: 27 | restart: unless-stopped 28 | 29 | prover-rpcd: 30 | restart: unless-stopped 31 | 32 | web: 33 | restart: unless-stopped 34 | ports: 35 | - 8000:8000 36 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | leader-testnet-geth: 5 | build: 6 | dockerfile: docker/geth/Dockerfile 7 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 8 | volumes: 9 | - leader-testnet-geth:/root 10 | environment: 11 | - MINER_PRIV_KEY 12 | - MINER_ADDRESS 13 | - GENESIS=l2-testnet 14 | # only used to get faster initial p2p connection setup 15 | - BOOTNODE=enode://61f076e9af396ecb5a0a8fb1a2c17491c3514f2abea0c228a3fcee49395339df2008fe201b37c7c0a565b775c5f8f5389b0b3de1701dc532c951a094af841cac@bootnode:30303 16 | command: 17 | --networkid 99 18 | --nodiscover 19 | --gcmode archive 20 | --unlock $MINER_ADDRESS 21 | --password /dev/null 22 | --allow-insecure-unlock 23 | --miner.gaslimit 300000 24 | --http 25 | --http.addr "[::]" 26 | --http.port 8545 27 | --http.corsdomain=* 28 | --http.vhosts=* 29 | --http.api eth,net,web3,txpool,miner,debug 30 | --nodekeyhex abebb96d7d9bbc99730439f230afd0008c0e0cb93eafb6874fecb256572252a4 31 | 32 | bootnode: 33 | depends_on: 34 | - leader-testnet-geth 35 | build: 36 | dockerfile: docker/geth/Dockerfile 37 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 38 | volumes: 39 | - bootnode:/root 40 | environment: 41 | - MINER_ADDRESS 42 | - GENESIS=l2-testnet 43 | - BOOTNODE=enode://f28f5a7706e5aec836f3136feb7d5e7264a7f0da04ac4984f0ff2421ee1dd2b135894cf0d4f5ff8c412442b95b9bb0780a9c8a8c64de2d4a8c458586fdb20829@leader-testnet-geth:30303 44 | command: 45 | --networkid 99 46 | --syncmode full 47 | --nodiscover 48 | --gcmode archive 49 | --light.serve 100 50 | --light.nosyncserve 51 | --nodekeyhex be6e66a76b664af9debbe2f4b3b9f21257fcce34412e10dfe383aabca9b4a9c0 52 | 53 | server-testnet-geth: 54 | depends_on: 55 | - bootnode 56 | deploy: 57 | replicas: 2 58 | build: 59 | dockerfile: docker/geth/Dockerfile 60 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 61 | environment: 62 | - MINER_ADDRESS 63 | - GENESIS=l2-testnet 64 | - BOOTNODE=enode://61f076e9af396ecb5a0a8fb1a2c17491c3514f2abea0c228a3fcee49395339df2008fe201b37c7c0a565b775c5f8f5389b0b3de1701dc532c951a094af841cac@bootnode:30303 65 | command: 66 | --networkid 99 67 | --syncmode light 68 | --nodiscover 69 | --http 70 | --http.addr "[::]" 71 | --http.port 8545 72 | --http.corsdomain=* 73 | --http.vhosts=* 74 | --http.api eth,net,web3,debug 75 | 76 | l1-testnet-geth: 77 | build: 78 | dockerfile: docker/geth/Dockerfile 79 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 80 | volumes: 81 | - l1-testnet-geth:/root 82 | environment: 83 | - MINER_PRIV_KEY 84 | - MINER_ADDRESS 85 | - GENESIS=l1-testnet 86 | command: 87 | --networkid 98 88 | --unlock $MINER_ADDRESS 89 | --password /dev/null 90 | --allow-insecure-unlock 91 | --nodiscover 92 | --miner.gaslimit 100000000000 93 | --mine 94 | --http 95 | --http.addr "[::]" 96 | --http.port 8545 97 | --http.corsdomain=* 98 | --http.vhosts=* 99 | --http.api eth,net,web3,debug 100 | --rpc.gascap 100000000000 101 | 102 | coordinator: 103 | init: true 104 | build: 105 | dockerfile: docker/coordinator/Dockerfile 106 | cache_from: 107 | - ghcr.io/privacy-scaling-explorations/zkevm-chain/coordinator-ci-cache:latest 108 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/coordinator:latest 109 | depends_on: 110 | - leader-testnet-geth 111 | - server-testnet-geth 112 | - l1-testnet-geth 113 | - prover-rpcd 114 | environment: 115 | - COORDINATOR_LISTEN=[::]:8545 116 | - COORDINATOR_RPC_SERVER_NODES=server-testnet-geth:8545 117 | - COORDINATOR_L2_RPC_URL=http://leader-testnet-geth:8545 118 | - COORDINATOR_L1_RPC_URL=http://l1-testnet-geth:8545 119 | - COORDINATOR_L1_BRIDGE=0x936a70c0b28532aa22240dce21f89a8399d6ac60 120 | - COORDINATOR_L1_PRIV=$MINER_PRIV_KEY 121 | - COORDINATOR_PROVER_RPCD_URL=http://prover-rpcd:8545 122 | - COORDINATOR_DUMMY_PROVER=${COORDINATOR_DUMMY_PROVER:-true} 123 | - COORDINATOR_ENABLE_FAUCET=true 124 | - COORDINATOR_CIRCUIT_NAME=super 125 | - COORDINATOR_UNSAFE_RPC=${COORDINATOR_UNSAFE_RPC:-false} 126 | 127 | prover-rpcd: 128 | init: true 129 | build: 130 | dockerfile: docker/prover/Dockerfile 131 | cache_from: 132 | - ghcr.io/privacy-scaling-explorations/zkevm-chain/prover-ci-cache:latest 133 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/prover:latest 134 | environment: 135 | - PROVERD_BIND=[::]:8545 136 | - PROVERD_LOOKUP=prover-rpcd:8545 137 | deploy: 138 | replicas: 1 139 | 140 | coverage-l1: 141 | profiles: 142 | - dev 143 | image: ghcr.io/pinkiebell/develatus-apparatus:v0.4.7 144 | volumes: 145 | - .:/host:ro 146 | working_dir: /host 147 | environment: 148 | - RPC=http://l1-testnet-geth:8545 149 | 150 | coverage-l2: 151 | profiles: 152 | - dev 153 | image: ghcr.io/pinkiebell/develatus-apparatus:v0.4.7 154 | volumes: 155 | - .:/host:ro 156 | working_dir: /host 157 | environment: 158 | - RPC=http://leader-testnet-geth:8545 159 | 160 | dev: 161 | profiles: 162 | - dev 163 | depends_on: 164 | - leader-testnet-geth 165 | - server-testnet-geth 166 | - l1-testnet-geth 167 | - coverage-l1 168 | - coverage-l2 169 | build: 170 | dockerfile: docker/dev/Dockerfile 171 | cache_from: 172 | - ghcr.io/privacy-scaling-explorations/zkevm-chain/dev-ci-cache:latest 173 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/dev:latest 174 | volumes: 175 | - .:/app 176 | - dev-build-cache:/target:overlay 177 | environment: 178 | - COORDINATOR_LISTEN=[::]:8000 179 | - COORDINATOR_RPC_SERVER_NODES=server-testnet-geth:8545 180 | - COORDINATOR_L1_RPC_URL=http://l1-testnet-geth:8545 181 | - COORDINATOR_L2_RPC_URL=http://leader-testnet-geth:8545 182 | - COORDINATOR_L1_BRIDGE=0x936a70c0b28532aa22240dce21f89a8399d6ac60 183 | - COORDINATOR_L1_PRIV=$MINER_PRIV_KEY 184 | - COORDINATOR_DUMMY_PROVER=${COORDINATOR_DUMMY_PROVER:-true} 185 | - COORDINATOR_ENABLE_FAUCET=true 186 | # useful env vars if running the proverd inside the dev image 187 | - PROVERD_LOOKUP=dev:8001 188 | - COORDINATOR_PROVER_RPCD_URL=http://dev:8001 189 | - PROVERD_BIND=[::]:8001 190 | - COORDINATOR_CIRCUIT_NAME=pi 191 | - COORDINATOR_UNSAFE_RPC=true 192 | - COORDINATOR_VERIFY_PROOF=true 193 | working_dir: /app 194 | entrypoint: /sbin/getty 195 | command: '-' 196 | tty: true 197 | init: true 198 | 199 | web: 200 | depends_on: 201 | - coordinator 202 | build: 203 | dockerfile: docker/web/Dockerfile 204 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/web:latest 205 | 206 | volumes: 207 | dev-build-cache: 208 | leader-testnet-geth: 209 | l1-testnet-geth: 210 | bootnode: 211 | -------------------------------------------------------------------------------- /docker/coordinator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c AS builder 2 | RUN apk add --no-cache rustup git musl-dev gcc binutils clang 3 | ENV CARGO_HOME=/usr/local/cargo 4 | ENV PATH=$CARGO_HOME/bin:$PATH 5 | ENV RUSTFLAGS='-C linker=rust-lld' 6 | ENV CC=/usr/bin/clang 7 | ENV AR=/usr/bin/ar 8 | 9 | ARG TARGETPLATFORM 10 | RUN \ 11 | case $TARGETPLATFORM in \ 12 | 'linux/amd64') arch=x86_64 ;; \ 13 | 'linux/arm64') arch=aarch64 ;; \ 14 | esac; \ 15 | printf "$arch-unknown-linux-musl" > /tmp/target; 16 | 17 | WORKDIR /target/src 18 | COPY rust-toolchain . 19 | RUN rustup-init -y --no-modify-path --profile minimal --default-toolchain $(cat rust-toolchain) --target $(cat /tmp/target) 20 | # trigger fetch of crates index 21 | RUN cargo search --limit 0 22 | 23 | COPY . . 24 | RUN cargo build --locked --bin coordinator --release --target-dir /target --target $(cat /tmp/target) && \ 25 | mv /target/*-unknown-linux-musl/release/coordinator / && rm -rf /target 26 | 27 | FROM alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c 28 | COPY --from=builder /coordinator / 29 | ENTRYPOINT ["/coordinator"] 30 | -------------------------------------------------------------------------------- /docker/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.15 AS solc 2 | RUN apk update && apk add boost-dev boost-static build-base cmake git 3 | 4 | ARG SOLC_VERSION="0.8.16" 5 | RUN git clone --depth 1 -b v"${SOLC_VERSION}" https://github.com/ethereum/solidity.git 6 | WORKDIR solidity/ 7 | RUN \ 8 | touch prerelease.txt && \ 9 | cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSOLC_LINK_STATIC=1 && \ 10 | make -j$(nproc) solc && \ 11 | strip solc/solc && \ 12 | mv solc/solc /solc && \ 13 | rm -rf $(pwd) 14 | 15 | # developer image 16 | # Should be alpine like the production images but fails due to 17 | # linkage bug(segfaults of test binaries w/ linked golang code) in rust. 18 | # Use debian until this is resolved. 19 | FROM debian:bookworm-slim 20 | COPY --from=solc /solc /usr/bin/solc 21 | ENV CARGO_TARGET_DIR=/target 22 | ENV CARGO_HOME=/usr/local/cargo 23 | ENV PATH=$CARGO_HOME/bin:$PATH 24 | COPY --chmod=444 rust-toolchain /tmp/rust-toolchain 25 | # adapted from official docker-rust 26 | RUN set -eux; \ 27 | apt-get update; \ 28 | apt-get install -y --no-install-recommends \ 29 | ca-certificates \ 30 | gcc \ 31 | libc6-dev \ 32 | wget \ 33 | golang \ 34 | pkg-config \ 35 | liblzma-dev \ 36 | procps \ 37 | jq \ 38 | curl \ 39 | git \ 40 | xz-utils \ 41 | time \ 42 | linux-perf \ 43 | libfontconfig-dev \ 44 | ; \ 45 | dpkgArch="$(dpkg --print-architecture)"; \ 46 | case "${dpkgArch##*-}" in \ 47 | amd64) rustArch='x86_64-unknown-linux-gnu'; rustupSha256='5cc9ffd1026e82e7fb2eec2121ad71f4b0f044e88bca39207b3f6b769aaa799c' ;; \ 48 | armhf) rustArch='armv7-unknown-linux-gnueabihf'; rustupSha256='48c5ecfd1409da93164af20cf4ac2c6f00688b15eb6ba65047f654060c844d85' ;; \ 49 | arm64) rustArch='aarch64-unknown-linux-gnu'; rustupSha256='e189948e396d47254103a49c987e7fb0e5dd8e34b200aa4481ecc4b8e41fb929' ;; \ 50 | i386) rustArch='i686-unknown-linux-gnu'; rustupSha256='0e0be29c560ad958ba52fcf06b3ea04435cb3cd674fbe11ce7d954093b9504fd' ;; \ 51 | *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \ 52 | esac; \ 53 | url="https://static.rust-lang.org/rustup/archive/1.25.1/${rustArch}/rustup-init"; \ 54 | wget "$url"; \ 55 | echo "${rustupSha256} *rustup-init" | sha256sum -c -; \ 56 | chmod +x rustup-init; \ 57 | ./rustup-init -y --no-modify-path --profile default --default-toolchain $(cat /tmp/rust-toolchain) --default-host ${rustArch}; \ 58 | rm rustup-init; 59 | # set default home to global cache directory 60 | ENV CARGO_HOME=/target/cargo 61 | -------------------------------------------------------------------------------- /docker/geth/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/privacy-scaling-explorations/go-ethereum:v1.10.23-zkevm 2 | COPY docker/geth/init.sh /init.sh 3 | COPY docker/geth/templates /templates 4 | ENTRYPOINT ["/init.sh"] 5 | -------------------------------------------------------------------------------- /docker/geth/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | DEFAULT_GETH_ARGS='' 6 | GENESIS_GENERATED='/root/genesis.json' 7 | GENESIS_TEMPLATE="/templates/$GENESIS.json" 8 | 9 | if [[ ! -e /root/.ethereum/geth ]]; then 10 | echo 'init chain' 11 | cat "$GENESIS_TEMPLATE" | sed "s/MINER_ADDRESS/$MINER_ADDRESS/g" > $GENESIS_GENERATED 12 | geth $DEFAULT_GETH_ARGS init $GENESIS_GENERATED 13 | fi 14 | 15 | if [[ ! -z $MINER_PRIV_KEY ]]; then 16 | geth $DEFAULT_GETH_ARGS --exec 'try { personal.importRawKey("'$MINER_PRIV_KEY'", null) } catch (e) { if (e.message !== "account already exists") { throw e; } }' console 17 | fi 18 | 19 | if [[ ! -z $BOOTNODE ]]; then 20 | cat > /geth.toml << EOF 21 | [Node.P2P] 22 | BootstrapNodes = ["$BOOTNODE"] 23 | StaticNodes = ["$BOOTNODE"] 24 | EOF 25 | 26 | DEFAULT_GETH_ARGS="$DEFAULT_GETH_ARGS --config /geth.toml" 27 | fi 28 | 29 | exec geth $DEFAULT_GETH_ARGS $@ 30 | -------------------------------------------------------------------------------- /docker/prover/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c AS builder 2 | RUN apk add --no-cache rustup git musl-dev gcc binutils clang go 3 | ENV CARGO_HOME=/usr/local/cargo 4 | ENV PATH=$CARGO_HOME/bin:$PATH 5 | ENV RUSTFLAGS='-C linker=rust-lld' 6 | ENV CC=/usr/bin/clang 7 | ENV AR=/usr/bin/ar 8 | 9 | ARG TARGETPLATFORM 10 | RUN \ 11 | case $TARGETPLATFORM in \ 12 | 'linux/amd64') arch=x86_64 ;; \ 13 | 'linux/arm64') arch=aarch64 ;; \ 14 | esac; \ 15 | printf "$arch-unknown-linux-musl" > /tmp/target; 16 | 17 | WORKDIR /target/src 18 | COPY rust-toolchain . 19 | RUN rustup-init -y --no-modify-path --profile minimal --default-toolchain $(cat rust-toolchain) --target $(cat /tmp/target) 20 | # trigger fetch of crates index 21 | RUN cargo search --limit 0 22 | 23 | COPY . . 24 | RUN cargo build --locked --bin prover_rpcd --release --target-dir /target --target $(cat /tmp/target) && \ 25 | mv /target/*-unknown-linux-musl/release/prover_rpcd / && rm -rf /target 26 | 27 | FROM alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c 28 | ENTRYPOINT ["/prover_rpcd"] 29 | COPY --from=builder /prover_rpcd / 30 | -------------------------------------------------------------------------------- /docker/web/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://github.com/privacy-scaling-explorations/hop/commit/e09a43ca270d4d0c7be63751c95ad037f1f68723 2 | FROM ghcr.io/privacy-scaling-explorations/hop/hop-frontend@sha256:cf8ef7061033fb2d8f349523baaee4b62859799e7e00d5be103023b0107b2b58 AS hop 3 | # nginx 1.21.6 4 | FROM nginx@sha256:2bcabc23b45489fb0885d69a06ba1d648aeda973fae7bb981bafbb884165e514 5 | COPY --from=hop /www /www 6 | COPY docker/web/nginx.conf /etc/nginx/nginx.conf 7 | -------------------------------------------------------------------------------- /docker/web/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | events { 5 | worker_connections 4096; 6 | } 7 | 8 | http { 9 | include /etc/nginx/mime.types; 10 | default_type application/octet-stream; 11 | index index.html; 12 | 13 | sendfile on; 14 | tcp_nopush on; 15 | keepalive_timeout 65; 16 | gzip_types text/plain text/html text/css text/xml image/svg+xml application/javascript application/json; 17 | 18 | server { 19 | listen 8000; 20 | absolute_redirect off; 21 | 22 | location / { 23 | # no brotli? 24 | gzip on; 25 | root /www/; 26 | try_files $uri /index.html; 27 | } 28 | 29 | location = /rpc/l1 { 30 | proxy_pass http://l1-testnet-geth:8545/; 31 | } 32 | 33 | location = /rpc/l2 { 34 | proxy_pass http://coordinator:8545/; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ### Testnet Setup 2 | The testnet setup consists of 6 entities where 2 of them are stateful (L1 node - 'upstream', L2 node - zkevm). 3 | The Ethereum accounts for L1/L2 Clique Block Producer and management tasks like cross chain message forwarding are done via the 4 | `MINER_PRIV_KEY` that has to be provided along `MINER_ADDRESS` - the Ethereum adress of that private key, in the `.env` file, see [.env.example][env-example] for guidance. 5 | 6 | ###### leader-testnet-geth 7 | The stateful L2 node `leader-testnet-geth` is used for block sealing and in general used for all L2 data that the `coordinator` needs. 8 | If changes are made to the [L2 Genesis Template][l2-genesis-template] then the L2 chain has to be initialized from scratch. 9 | 10 | ###### server-testnet-geth 11 | Volatile geth nodes that sync from `leader-testnet-geth` to serve JSON-RPC requests coming from the `coordinator`. 12 | This entity can be dynamically scaled down or up and the `coordinator` checks the healthiness at periodic intervals and forwards requests to these nodes at random. 13 | The `coordinator` periodically queries the Docker DNS service to get a list of active instances and maintains that list accordingly. 14 | 15 | ###### l1-testnet-geth 16 | This node resembles the upstream or mainnet node and contains the relevant L1 Bridge contract(s) for zkEVM. 17 | If changes are made to the [L1 Genesis Template][l1-genesis-template] then the L1 chain has to be initialized from scratch. 18 | 19 | ###### coordinator 20 | This daemon handles all relevant tasks: 21 | - sealing L2 blocks 22 | - submitting L2 blocks to L1 bridge 23 | - requesting/computing proofs and finalizing L2 blocks on the L1 bridge 24 | - Execution of L2 to L1 and L1 to L2 cross chain messages. 25 | - Checking the healthiness and forwarding L2 JSON-RPC requests to a set of `server-testnet-geth` nodes. 26 | 27 | ###### web 28 | Serves the hop-protocol webapp and provides proxies at the following paths via nginx: 29 | - `/rpc/l1` to `l1-testnet-geth` 30 | - `/rpc/l2` to `coordinator` that in turn chooses a replica of `server-testnet-geth` at random. 31 | 32 | ### Layer 1 - Bridge 33 | 34 | The [`ZkEvmL1Bridge`][ZkEvmL1Bridge] is responsible for 35 | - submission and finalisation of L2 blocks 36 | - cross chain messaging 37 | 38 | Sending messages and/or ETH to L2 requires calling [`dispatchMessage`][IZkEvmMessageDispatcher] on the [`ZkEvmL1Bridge`][ZkEvmL1Bridge]. 39 | 40 | Receiving messages from L2 to L1 requires waiting until the corresponding L2 block that includes the given message is finalized on L1 and then calling 41 | [`deliverMessageWithProof`][IZkEvmMessageDelivererWithProof] on the L1 bridge. 42 | 43 | Messages can also be dropped to reclaim ETH if they exceed the message `deadline` via [`dropMessage`][IZkEvmMessageDispatcher]. 44 | 45 | ###### Addresses on L1 testnet 46 | - [`ZkEvmL1Bridge`][ZkEvmL1Bridge] 47 | - `936a70c0b28532aa22240dce21f89a8399d6ac60` 48 | - [`L1OptimismBridge`][L1OptimismBridge] - A Optimisms `ICrossDomainMessenger` compatibility contract 49 | - `936a70c0b28532aa22240dce21f89a8399d6ac61` 50 | 51 | ### Layer 2 - Bridge 52 | 53 | There are two zkEVM related bridge contracts on L2: 54 | 55 | - [`ZkEvmL2MessageDeliverer`][ZkEvmL2MessageDeliverer] 56 | - `0000000000000000000000000000000000010000` 57 | - [`ZkEvmL2MessageDispatcher`][ZkEvmL2MessageDispatcher] 58 | - `0000000000000000000000000000000000020000` 59 | 60 | The `ZkEvmL2MessageDeliverer` is responsible for processing messages from L1 to L2 and holds `uint256(-1)` ETH to allow for deposits to happen in regular transactions. 61 | Messages from L2 to L1 can be invoked via calling [`dispatchMessage`][IZkEvmMessageDispatcher] on [`ZkEvmL2MessageDispatcher`][ZkEvmL2MessageDispatcher]. A message can be delivered on L1 via `deliverMessageWithProof` once the transaction was included in a L2 Block and finalized on L1. 62 | 63 | **Note**: The Coordinator **MUST** make sure that no transaction to the `ZkEvmL2MessageDeliverer` is made in regular L2 Blocks. It's the responsibility of the Coordinator to build blocks with [`deliverMessage`][IZkEvmMessageDelivererWithoutProof] on [`ZkEvmL2MessageDeliverer`][ZkEvmL2MessageDeliverer]. This **MUST** also be enforced on the [`ZkEvmL1Bridge`][ZkEvmL1Bridge]. 64 | 65 | ### Layer 2 - go-ethereum 66 | 67 | The [geth fork][geth-fork] aims to be even with - or as close as possible to upstream. 68 | The zkEVM fork option depends on [Berlin](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md) 69 | and disables: 70 | - [EIP-2718: Typed Transaction Envelope](https://eips.ethereum.org/EIPS/eip-2718) 71 | - [EIP-2930: Optional access lists](https://eips.ethereum.org/EIPS/eip-2930) 72 | 73 | In addition, zkEVM enables these EIPs included in `london`: 74 | - [EIP-3541: Reject new contracts starting with the 0xEF byte](https://eips.ethereum.org/EIPS/eip-3541) 75 | - [EIP-3529: Reduction in refunds](https://eips.ethereum.org/EIPS/eip-3529) 76 | 77 | The zkEVM runs on the `Clique` consensus with one single proposer and the block submission to `ZkEvmL1Bridge` is permissioned. 78 | **TBD** 79 | Additionaly, only partial data of each L2 Block is submitted on L1 and therefore the data availability is weak. 80 | 81 | ###### Resources 82 | - https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/ 83 | 84 | ### Layer 2 - Additional JSON-RPC methods 85 | - `miner_init` 86 | - initializes the miner without starting mining tasks 87 | - `miner_setHead` [blockHash] 88 | - updates the canonical chain and announces the block on the p2p layer 89 | - `miner_sealBlock` [{ parent, random, timestamp, transactions }] 90 | - mines and seals a block without changing the canonical chain. 91 | If `transactions` is not nil then produces a block with only those transactions. If nil, then it consumes from the transaction pool. 92 | Returns the block if successful. 93 | 94 | ### Layer 2 - Genesis Contracts 95 | - [zkEVM Layer 2 Message Deliverer][ZkEvmL2MessageDeliverer] 96 | - `0000000000000000000000000000000000010000` 97 | - [zkEVM Layer 2 Message Dispatcher][ZkEvmL2MessageDispatcher] 98 | - `0000000000000000000000000000000000020000` 99 | - [Optimism WETH9](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000006) 100 | - `4200000000000000000000000000000000000006` 101 | - [Optimism L2CrossDomainMessenger](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000007) 102 | - `4200000000000000000000000000000000000007` 103 | - [Optimism L2StandardBridge](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000010) 104 | - `4200000000000000000000000000000000000010` 105 | 106 | ###### Resources 107 | - [EIP-1352: Specify restricted address range for precompiles/system contracts](https://eips.ethereum.org/EIPS/eip-1352) 108 | - https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments/mainnet#readme 109 | 110 | ### Layer 2 - ETH genesis allocations 111 | - `L2_CLIQUE_PROPOSER` (0.1 ETH) as initial balance to allow for deposits to be delivered. 112 | **The ZkEvmL1Bridge **MUST** hold the initial amount to account for the L2 balance of the L2_CLIQUE_PROPOSER** 113 | - `0x000000000000000000000000000000000000000000000000016345785d8a0000` 114 | - `ZkEvmL2MessageDeliverer` - `uint256(-1)` minus the initial balance for `L2_CLIQUE_PROPOSER` 115 | - `0xfffffffffffffffffffffffffffffffffffffffffffffffffe9cba87a275ffff` 116 | 117 | ### Coordinator 118 | The Coordinator has the following responsibilities: 119 | - Keeping track of `ZkEvmL1Bridge`, `ZkEvmL2MessageDeliverer` and `ZkEvmL2MessageDispatcher` events. 120 | - Importing any missing data to the L2 nodes. **TBD** 121 | - Mining new blocks and setting the canonical chain head. 122 | - Relaying L1 to L2 and L2 to L1 messages. 123 | - Computing proofs for L2 Blocks. 124 | - Submitting and finalizing L2 blocks on the `ZkEvmL1Bridge`. 125 | - Acts as a round-robin proxy to serve JSON-RPC over a set of healthy l2-nodes. 126 | 127 | ###### Syncing Phase 128 | ```mermaid 129 | flowchart LR 130 | sync --> l1-geth --> l1-bridge-events --> coordinator 131 | sync --> l2-geth --> l2-bridge-events --> coordinator 132 | ``` 133 | ###### Mining Phase 134 | ```mermaid 135 | flowchart LR 136 | mine_l1_to_l2_messages --> L1-MessageDispatched-events --> ZkEvmL2MessageDeliverer --> deliverMessage --> miner_sealBlock --> miner_setHead 137 | mine --> tx_pool_pending? --> miner_sealBlock --> verify_block --> miner_setHead 138 | ``` 139 | 140 | [IZkEvmMessageDispatcher]: ../contracts/interfaces/IZkEvmMessageDispatcher.sol 141 | [ZkEvmL2MessageDispatcher]: ../contracts/ZkEvmL2MessageDispatcher.sol 142 | [ZkEvmL2MessageDeliverer]: ../contracts/ZkEvmL2MessageDeliverer.sol 143 | [IZkEvmMessageDelivererWithProof]: ../contracts/interfaces/IZkEvmMessageDelivererWithProof.sol 144 | [IZkEvmMessageDelivererWithoutProof]: ../contracts/interfaces/IZkEvmMessageDelivererWithoutProof.sol 145 | [ZkEvmL1Bridge]: ../contracts/ZkEvmL1Bridge.sol 146 | [l1-genesis-template]: ../docker/geth/templates/l1-testnet.json 147 | [l2-genesis-template]: ../docker/geth/templates/l2-testnet.json 148 | [env-example]: ../.env.example 149 | [L1OptimismBridge]: ../contracts/optimism/L1OptimismBridge.sol 150 | [geth-fork]: https://github.com/privacy-scaling-explorations/go-ethereum 151 | -------------------------------------------------------------------------------- /prover/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prover" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2022_10_22" } 9 | bus-mapping = { git = "https://github.com/pinkiebell/zkevm-circuits.git", branch = "zkevm-chain" } 10 | eth-types = { git = "https://github.com/pinkiebell/zkevm-circuits.git", branch = "zkevm-chain" } 11 | zkevm-circuits = { git = "https://github.com/pinkiebell/zkevm-circuits.git", branch = "zkevm-chain", features = ["test"] } 12 | plonk_verifier = { git = "https://github.com/privacy-scaling-explorations/plonk-verifier.git", rev = "ba167b73ca09d77ab08958d4a4b4e0433222e4db" } 13 | env_logger = "0.9.0" 14 | ethers-providers = "0.17.0" 15 | hyper = { version = "0.14.16", features = ["server"] } 16 | rand_xorshift = "0.3" 17 | log = "0.4.14" 18 | rand = "0.8.4" 19 | serde = { version = "1.0.136", features = ["derive"] } 20 | serde_json = "1.0.78" 21 | strum = "0.24" 22 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread"] } 23 | zkevm_common = { path = "../common" } 24 | itertools = "0.10.3" 25 | clap = { version = "4.0.14", features = ["derive", "env"] } 26 | 27 | # autogen 28 | mock = { git = "https://github.com/pinkiebell/zkevm-circuits.git", branch = "zkevm-chain", optional = true } 29 | ethers-signers = { version = "0.17.0", optional = true } 30 | zkevm_dev = { path = "../dev", optional = true } 31 | 32 | [features] 33 | default = [] 34 | autogen = ["mock", "ethers-signers", "zkevm_dev"] 35 | -------------------------------------------------------------------------------- /prover/src/aggregation_circuit.rs: -------------------------------------------------------------------------------- 1 | use crate::ProverParams; 2 | use halo2_proofs::{ 3 | circuit::{Layouter, SimpleFloorPlanner, Value}, 4 | halo2curves::bn256::{Bn256, Fq, Fr, G1Affine}, 5 | plonk::{self, Circuit, ConstraintSystem}, 6 | poly::commitment::ParamsProver, 7 | }; 8 | use itertools::Itertools; 9 | use plonk_verifier::loader::halo2::halo2_wrong_ecc::{ 10 | self, 11 | integer::rns::Rns, 12 | maingate::{ 13 | MainGate, MainGateConfig, MainGateInstructions, RangeChip, RangeConfig, RangeInstructions, 14 | RegionCtx, 15 | }, 16 | EccConfig, 17 | }; 18 | use plonk_verifier::{ 19 | loader::{self, native::NativeLoader}, 20 | pcs::{ 21 | kzg::{ 22 | Gwc19, Kzg, KzgAccumulator, KzgAs, KzgSuccinctVerifyingKey, LimbsEncoding, 23 | LimbsEncodingInstructions, 24 | }, 25 | AccumulationScheme, AccumulationSchemeProver, 26 | }, 27 | system, 28 | util::arithmetic::{fe_to_limbs, FieldExt}, 29 | verifier::{self, PlonkVerifier}, 30 | Protocol, 31 | }; 32 | use rand::Rng; 33 | use std::rc::Rc; 34 | 35 | const LIMBS: usize = 4; 36 | const BITS: usize = 68; 37 | pub type Pcs = Kzg; 38 | type As = KzgAs; 39 | pub type Plonk = verifier::Plonk>; 40 | 41 | const T: usize = 5; 42 | const RATE: usize = 4; 43 | const R_F: usize = 8; 44 | const R_P: usize = 60; 45 | 46 | type Svk = KzgSuccinctVerifyingKey; 47 | type BaseFieldEccChip = halo2_wrong_ecc::BaseFieldEccChip; 48 | type Halo2Loader<'a> = loader::halo2::Halo2Loader<'a, G1Affine, BaseFieldEccChip>; 49 | pub type PoseidonTranscript = 50 | system::halo2::transcript::halo2::PoseidonTranscript; 51 | 52 | pub struct Snark { 53 | pub protocol: Protocol, 54 | pub instances: Vec>, 55 | pub proof: Vec, 56 | } 57 | 58 | impl Snark { 59 | pub fn new(protocol: Protocol, instances: Vec>, proof: Vec) -> Self { 60 | Self { 61 | protocol, 62 | instances, 63 | proof, 64 | } 65 | } 66 | } 67 | 68 | impl From for SnarkWitness { 69 | fn from(snark: Snark) -> Self { 70 | Self { 71 | protocol: snark.protocol, 72 | instances: snark 73 | .instances 74 | .into_iter() 75 | .map(|instances| instances.into_iter().map(Value::known).collect_vec()) 76 | .collect(), 77 | proof: Value::known(snark.proof), 78 | } 79 | } 80 | } 81 | 82 | #[derive(Clone)] 83 | pub struct SnarkWitness { 84 | protocol: Protocol, 85 | instances: Vec>>, 86 | proof: Value>, 87 | } 88 | 89 | impl SnarkWitness { 90 | fn without_witnesses(&self) -> Self { 91 | SnarkWitness { 92 | protocol: self.protocol.clone(), 93 | instances: self 94 | .instances 95 | .iter() 96 | .map(|instances| vec![Value::unknown(); instances.len()]) 97 | .collect(), 98 | proof: Value::unknown(), 99 | } 100 | } 101 | 102 | fn proof(&self) -> Value<&[u8]> { 103 | self.proof.as_ref().map(Vec::as_slice) 104 | } 105 | } 106 | 107 | pub fn aggregate<'a>( 108 | svk: &Svk, 109 | loader: &Rc>, 110 | snarks: &[SnarkWitness], 111 | as_proof: Value<&'_ [u8]>, 112 | ) -> KzgAccumulator>> { 113 | let assign_instances = |instances: &[Vec>]| { 114 | instances 115 | .iter() 116 | .map(|instances| { 117 | instances 118 | .iter() 119 | .map(|instance| loader.assign_scalar(*instance)) 120 | .collect_vec() 121 | }) 122 | .collect_vec() 123 | }; 124 | 125 | let accumulators = snarks 126 | .iter() 127 | .flat_map(|snark| { 128 | let protocol = snark.protocol.loaded(loader); 129 | let instances = assign_instances(&snark.instances); 130 | let mut transcript = 131 | PoseidonTranscript::, _>::new(loader, snark.proof()); 132 | let proof = Plonk::read_proof(svk, &protocol, &instances, &mut transcript).unwrap(); 133 | Plonk::succinct_verify(svk, &protocol, &instances, &proof).unwrap() 134 | }) 135 | .collect_vec(); 136 | 137 | let acccumulator = { 138 | let mut transcript = PoseidonTranscript::, _>::new(loader, as_proof); 139 | let proof = As::read_proof(&Default::default(), &accumulators, &mut transcript).unwrap(); 140 | As::verify(&Default::default(), &accumulators, &proof).unwrap() 141 | }; 142 | 143 | acccumulator 144 | } 145 | 146 | #[derive(Clone)] 147 | pub struct AggregationConfig { 148 | main_gate_config: MainGateConfig, 149 | range_config: RangeConfig, 150 | } 151 | 152 | impl AggregationConfig { 153 | pub fn configure( 154 | meta: &mut ConstraintSystem, 155 | composition_bits: Vec, 156 | overflow_bits: Vec, 157 | ) -> Self { 158 | let main_gate_config = MainGate::::configure(meta); 159 | let range_config = 160 | RangeChip::::configure(meta, &main_gate_config, composition_bits, overflow_bits); 161 | AggregationConfig { 162 | main_gate_config, 163 | range_config, 164 | } 165 | } 166 | 167 | pub fn main_gate(&self) -> MainGate { 168 | MainGate::new(self.main_gate_config.clone()) 169 | } 170 | 171 | pub fn range_chip(&self) -> RangeChip { 172 | RangeChip::new(self.range_config.clone()) 173 | } 174 | 175 | pub fn ecc_chip(&self) -> BaseFieldEccChip { 176 | BaseFieldEccChip::new(EccConfig::new( 177 | self.range_config.clone(), 178 | self.main_gate_config.clone(), 179 | )) 180 | } 181 | } 182 | 183 | #[derive(Clone)] 184 | pub struct AggregationCircuit { 185 | svk: Svk, 186 | snarks: Vec, 187 | instances: Vec, 188 | as_proof: Value>, 189 | } 190 | 191 | impl AggregationCircuit { 192 | pub fn new( 193 | params: &ProverParams, 194 | snarks: impl IntoIterator, 195 | mut rng: RNG, 196 | ) -> Self { 197 | let svk = params.get_g()[0].into(); 198 | let snarks = snarks.into_iter().collect_vec(); 199 | 200 | let accumulators = snarks 201 | .iter() 202 | .flat_map(|snark| { 203 | let mut transcript = 204 | PoseidonTranscript::::new(snark.proof.as_slice()); 205 | let proof = 206 | Plonk::read_proof(&svk, &snark.protocol, &snark.instances, &mut transcript) 207 | .unwrap(); 208 | Plonk::succinct_verify(&svk, &snark.protocol, &snark.instances, &proof).unwrap() 209 | }) 210 | .collect_vec(); 211 | 212 | let (accumulator, as_proof) = { 213 | let mut transcript = PoseidonTranscript::::new(Vec::new()); 214 | let accumulator = As::create_proof( 215 | &Default::default(), 216 | &accumulators, 217 | &mut transcript, 218 | &mut rng, 219 | ) 220 | .unwrap(); 221 | (accumulator, transcript.finalize()) 222 | }; 223 | 224 | let KzgAccumulator { lhs, rhs } = accumulator; 225 | let instances = [lhs.x, lhs.y, rhs.x, rhs.y] 226 | .map(fe_to_limbs::<_, _, LIMBS, BITS>) 227 | .concat(); 228 | 229 | Self { 230 | svk, 231 | snarks: snarks.into_iter().map_into().collect(), 232 | instances, 233 | as_proof: Value::known(as_proof), 234 | } 235 | } 236 | 237 | pub fn accumulator_indices() -> Vec<(usize, usize)> { 238 | (0..4 * LIMBS).map(|idx| (0, idx)).collect() 239 | } 240 | 241 | pub fn num_instance() -> Vec { 242 | vec![4 * LIMBS] 243 | } 244 | 245 | pub fn instance(&self) -> Vec> { 246 | vec![self.instances.clone()] 247 | } 248 | 249 | pub fn as_proof(&self) -> Value<&[u8]> { 250 | self.as_proof.as_ref().map(Vec::as_slice) 251 | } 252 | } 253 | 254 | impl Circuit for AggregationCircuit { 255 | type Config = AggregationConfig; 256 | type FloorPlanner = SimpleFloorPlanner; 257 | 258 | fn without_witnesses(&self) -> Self { 259 | Self { 260 | svk: self.svk, 261 | snarks: self 262 | .snarks 263 | .iter() 264 | .map(SnarkWitness::without_witnesses) 265 | .collect(), 266 | instances: Vec::new(), 267 | as_proof: Value::unknown(), 268 | } 269 | } 270 | 271 | fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { 272 | AggregationConfig::configure( 273 | meta, 274 | vec![BITS / LIMBS], 275 | Rns::::construct().overflow_lengths(), 276 | ) 277 | } 278 | 279 | fn synthesize( 280 | &self, 281 | config: Self::Config, 282 | mut layouter: impl Layouter, 283 | ) -> Result<(), plonk::Error> { 284 | let main_gate = config.main_gate(); 285 | let range_chip = config.range_chip(); 286 | 287 | range_chip.load_table(&mut layouter)?; 288 | 289 | let accumulator_limbs = layouter.assign_region( 290 | || "", 291 | |region| { 292 | let ctx = RegionCtx::new(region, 0); 293 | 294 | let ecc_chip = config.ecc_chip(); 295 | let loader = Halo2Loader::new(ecc_chip, ctx); 296 | let accumulator = aggregate(&self.svk, &loader, &self.snarks, self.as_proof()); 297 | 298 | let accumulator_limbs = [accumulator.lhs, accumulator.rhs] 299 | .iter() 300 | .map(|ec_point| { 301 | loader 302 | .ecc_chip() 303 | .assign_ec_point_to_limbs(&mut loader.ctx_mut(), ec_point.assigned()) 304 | }) 305 | .collect::, plonk::Error>>()? 306 | .into_iter() 307 | .flatten(); 308 | 309 | Ok(accumulator_limbs) 310 | }, 311 | )?; 312 | 313 | for (row, limb) in accumulator_limbs.enumerate() { 314 | main_gate.expose_public(layouter.namespace(|| ""), limb, row)?; 315 | } 316 | 317 | Ok(()) 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /prover/src/bin/gen_params.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::poly::commitment::Params; 2 | use prover::ProverParams; 3 | use rand::rngs::OsRng; 4 | use std::env; 5 | use std::fs::File; 6 | use std::io::Write; 7 | 8 | /// This utility supports parameter generation. 9 | /// Can be invoked with: gen_params 10 | fn main() { 11 | let mut args = env::args(); 12 | let params_path: String = args.next_back().expect("path to file"); 13 | let degree: u32 = args 14 | .next_back() 15 | .expect("degree") 16 | .parse::() 17 | .expect("valid number"); 18 | let mut file = File::create(¶ms_path).expect("Failed to create file"); 19 | 20 | println!("Generating params with degree: {}", degree); 21 | 22 | let general_params = ProverParams::setup(degree, OsRng); 23 | let mut buf = Vec::new(); 24 | general_params 25 | .write(&mut buf) 26 | .expect("Failed to write params"); 27 | file.write_all(&buf[..]) 28 | .expect("Failed to write params to file"); 29 | 30 | println!("Written to {}", params_path); 31 | } 32 | -------------------------------------------------------------------------------- /prover/src/bin/prover_cmd.rs: -------------------------------------------------------------------------------- 1 | use env_logger::Env; 2 | use prover::shared_state::SharedState; 3 | use std::env::var; 4 | use zkevm_common::prover::*; 5 | 6 | /// This command generates and prints the proofs to stdout. 7 | /// Required environment variables: 8 | /// - PROVERD_BLOCK_NUM - the block number to generate the proof for 9 | /// - PROVERD_RPC_URL - a geth http rpc that supports the debug namespace 10 | /// - PROVERD_PARAMS_PATH - a path to a file generated with the gen_params tool 11 | #[tokio::main] 12 | async fn main() { 13 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 14 | 15 | let block_num: u64 = var("PROVERD_BLOCK_NUM") 16 | .expect("PROVERD_BLOCK_NUM env var") 17 | .parse() 18 | .expect("Cannot parse PROVERD_BLOCK_NUM env var"); 19 | let rpc_url: String = var("PROVERD_RPC_URL") 20 | .expect("PROVERD_RPC_URL env var") 21 | .parse() 22 | .expect("Cannot parse PROVERD_RPC_URL env var"); 23 | let params_path: String = var("PROVERD_PARAMS_PATH") 24 | .expect("PROVERD_PARAMS_PATH env var") 25 | .parse() 26 | .expect("Cannot parse PROVERD_PARAMS_PATH env var"); 27 | 28 | let state = SharedState::new(String::new(), None); 29 | let request = ProofRequestOptions { 30 | circuit: "super".to_string(), 31 | block: block_num, 32 | rpc: rpc_url, 33 | retry: false, 34 | param: Some(params_path), 35 | mock: false, 36 | aggregate: false, 37 | ..Default::default() 38 | }; 39 | 40 | state.get_or_enqueue(&request).await; 41 | state.duty_cycle().await; 42 | let result = state 43 | .get_or_enqueue(&request) 44 | .await 45 | .expect("some") 46 | .expect("result"); 47 | 48 | serde_json::to_writer(std::io::stdout(), &result).expect("serialize and write"); 49 | } 50 | -------------------------------------------------------------------------------- /prover/src/bin/prover_rpcd.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use env_logger::Env; 3 | 4 | use prover::server::serve; 5 | use prover::shared_state::SharedState; 6 | 7 | #[derive(Parser, Debug)] 8 | #[clap(version, about)] 9 | /// This command starts a http/json-rpc server and serves proof oriented methods. 10 | pub(crate) struct ProverdConfig { 11 | #[clap(long, env = "PROVERD_BIND")] 12 | /// The interface address + port combination to accept connections on, 13 | /// e.g. `[::]:1234`. 14 | bind: String, 15 | #[clap(long, env = "PROVERD_LOOKUP")] 16 | /// A `HOSTNAME:PORT` conformant string that will be used for DNS service discovery of other nodes. 17 | lookup: String, 18 | } 19 | 20 | #[tokio::main] 21 | async fn main() { 22 | let config = ProverdConfig::parse(); 23 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 24 | 25 | let shared_state = SharedState::new(SharedState::random_worker_id(), Some(config.lookup)); 26 | { 27 | // start the http server 28 | let h1 = serve(&shared_state, &config.bind); 29 | 30 | // starts the duty cycle loop 31 | let ctx = shared_state.clone(); 32 | // use a dedicated runtime for mixed async / heavy (blocking) compute 33 | let rt = tokio::runtime::Builder::new_multi_thread() 34 | .enable_all() 35 | .build() 36 | .unwrap(); 37 | let h2 = rt.spawn(async move { 38 | loop { 39 | let ctx = ctx.clone(); 40 | // enclose this call to catch panics which may 41 | // occur due to network services 42 | let _ = tokio::spawn(async move { 43 | log::debug!("task: duty_cycle"); 44 | ctx.duty_cycle().await; 45 | }) 46 | .await; 47 | tokio::time::sleep(std::time::Duration::from_millis(1000)).await; 48 | } 49 | }); 50 | 51 | // this task loop makes sure to merge task results periodically 52 | // even if this instance is busy with proving 53 | let ctx = shared_state.clone(); 54 | let h3 = tokio::spawn(async move { 55 | loop { 56 | let ctx = ctx.clone(); 57 | // enclose this call to catch panics which may 58 | // occur due to network services 59 | let _ = tokio::spawn(async move { 60 | log::debug!("task: merge_tasks_from_peers"); 61 | let _ = ctx.merge_tasks_from_peers().await; 62 | }) 63 | .await; 64 | tokio::time::sleep(std::time::Duration::from_millis(1000)).await; 65 | } 66 | }); 67 | 68 | // wait for all tasks 69 | if tokio::try_join!(h1, h2, h3).is_err() { 70 | panic!("unexpected task error"); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /prover/src/circuit_autogen.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! match_circuit_params { 3 | ($gas_used:expr, $on_match:expr, $on_error:expr) => { 4 | match $gas_used { 5 | 0..=63000 => { 6 | const CIRCUIT_CONFIG: CircuitConfig = CircuitConfig { 7 | block_gas_limit: 63000, 8 | max_txs: 3, 9 | max_calldata: 10500, 10 | max_bytecode: 24634, 11 | max_rws: 476052, 12 | min_k: 20, 13 | pad_to: 476052, 14 | min_k_aggregation: 26, 15 | keccak_padding: 336000, 16 | }; 17 | $on_match 18 | } 19 | 63001..=300000 => { 20 | const CIRCUIT_CONFIG: CircuitConfig = CircuitConfig { 21 | block_gas_limit: 300000, 22 | max_txs: 14, 23 | max_calldata: 69750, 24 | max_bytecode: 139500, 25 | max_rws: 3161966, 26 | min_k: 23, 27 | pad_to: 3161966, 28 | min_k_aggregation: 26, 29 | keccak_padding: 1600000, 30 | }; 31 | $on_match 32 | } 33 | 34 | _ => $on_error, 35 | } 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /prover/src/circuit_witness.rs: -------------------------------------------------------------------------------- 1 | use bus_mapping::circuit_input_builder::BuilderClient; 2 | use bus_mapping::circuit_input_builder::CircuitsParams; 3 | use bus_mapping::mock::BlockData; 4 | use bus_mapping::rpc::GethClient; 5 | use eth_types::geth_types; 6 | use eth_types::geth_types::GethData; 7 | use eth_types::Address; 8 | use eth_types::ToBigEndian; 9 | use eth_types::Word; 10 | use eth_types::H256; 11 | use ethers_providers::Http; 12 | use halo2_proofs::halo2curves::bn256::Fr; 13 | use std::str::FromStr; 14 | use zkevm_circuits::evm_circuit; 15 | use zkevm_circuits::pi_circuit::PublicData; 16 | use zkevm_common::prover::CircuitConfig; 17 | 18 | /// Wrapper struct for circuit witness data. 19 | pub struct CircuitWitness { 20 | pub circuit_config: CircuitConfig, 21 | pub eth_block: eth_types::Block, 22 | pub block: bus_mapping::circuit_input_builder::Block, 23 | pub code_db: bus_mapping::state_db::CodeDB, 24 | } 25 | 26 | impl CircuitWitness { 27 | pub fn dummy(circuit_config: CircuitConfig) -> Result { 28 | let history_hashes = vec![Word::zero(); 256]; 29 | let mut eth_block: eth_types::Block = eth_types::Block::default(); 30 | eth_block.author = Some(Address::zero()); 31 | eth_block.number = Some(history_hashes.len().into()); 32 | eth_block.base_fee_per_gas = Some(0.into()); 33 | eth_block.hash = Some(eth_block.parent_hash); 34 | eth_block.gas_limit = circuit_config.block_gas_limit.into(); 35 | 36 | let circuit_params = CircuitsParams { 37 | max_txs: circuit_config.max_txs, 38 | max_calldata: circuit_config.max_calldata, 39 | max_bytecode: circuit_config.max_bytecode, 40 | max_rws: circuit_config.max_rws, 41 | keccak_padding: Some(circuit_config.keccak_padding), 42 | }; 43 | let empty_data = GethData { 44 | chain_id: Word::from(99), 45 | history_hashes: vec![Word::zero(); 256], 46 | eth_block, 47 | geth_traces: Vec::new(), 48 | accounts: Vec::new(), 49 | }; 50 | let mut builder = 51 | BlockData::new_from_geth_data_with_params(empty_data.clone(), circuit_params) 52 | .new_circuit_input_builder(); 53 | builder 54 | .handle_block(&empty_data.eth_block, &empty_data.geth_traces) 55 | .unwrap(); 56 | Ok(Self { 57 | circuit_config, 58 | eth_block: empty_data.eth_block, 59 | block: builder.block, 60 | code_db: builder.code_db, 61 | }) 62 | } 63 | 64 | /// Gathers debug trace(s) from `rpc_url` for block `block_num`. 65 | /// Expects a go-ethereum node with debug & archive capabilities on `rpc_url`. 66 | pub async fn from_rpc( 67 | block_num: &u64, 68 | rpc_url: &str, 69 | ) -> Result> { 70 | let url = Http::from_str(rpc_url)?; 71 | let geth_client = GethClient::new(url); 72 | // TODO: add support for `eth_getHeaderByNumber` 73 | let block = geth_client.get_block_by_number((*block_num).into()).await?; 74 | let circuit_config = 75 | crate::match_circuit_params!(block.gas_used.as_usize(), CIRCUIT_CONFIG, { 76 | return Err(format!( 77 | "No circuit parameters found for block with gas used={}", 78 | block.gas_used 79 | ) 80 | .into()); 81 | }); 82 | let circuit_params = CircuitsParams { 83 | max_txs: circuit_config.max_txs, 84 | max_calldata: circuit_config.max_calldata, 85 | max_bytecode: circuit_config.max_bytecode, 86 | max_rws: circuit_config.max_rws, 87 | keccak_padding: Some(circuit_config.keccak_padding), 88 | }; 89 | let builder = BuilderClient::new(geth_client, circuit_params).await?; 90 | let (builder, eth_block) = builder.gen_inputs(*block_num).await?; 91 | 92 | Ok(Self { 93 | circuit_config, 94 | eth_block, 95 | block: builder.block, 96 | code_db: builder.code_db, 97 | }) 98 | } 99 | 100 | pub fn evm_witness(&self) -> zkevm_circuits::witness::Block { 101 | let mut block = 102 | evm_circuit::witness::block_convert(&self.block, &self.code_db).expect("block_convert"); 103 | block.evm_circuit_pad_to = self.circuit_config.pad_to; 104 | block.exp_circuit_pad_to = self.circuit_config.pad_to; 105 | // expect mock randomness 106 | assert_eq!(block.randomness, Fr::from(0x100)); 107 | 108 | block 109 | } 110 | 111 | pub fn gas_used(&self) -> u64 { 112 | self.eth_block.gas_used.as_u64() 113 | } 114 | 115 | pub fn txs(&self) -> Vec { 116 | let txs = self 117 | .eth_block 118 | .transactions 119 | .iter() 120 | .map(geth_types::Transaction::from) 121 | .collect(); 122 | 123 | txs 124 | } 125 | 126 | pub fn public_data(&self) -> PublicData { 127 | let chain_id = self.block.chain_id; 128 | let eth_block = self.eth_block.clone(); 129 | let history_hashes = self.block.history_hashes.clone(); 130 | let block_constants = geth_types::BlockConstants { 131 | coinbase: eth_block.author.expect("coinbase"), 132 | timestamp: eth_block.timestamp, 133 | number: eth_block.number.expect("number"), 134 | difficulty: eth_block.difficulty, 135 | gas_limit: eth_block.gas_limit, 136 | base_fee: eth_block.base_fee_per_gas.unwrap_or_default(), 137 | }; 138 | let prev_state_root = H256::from(self.block.prev_state_root.to_be_bytes()); 139 | 140 | PublicData { 141 | chain_id, 142 | history_hashes, 143 | block_constants, 144 | prev_state_root, 145 | transactions: eth_block.transactions.clone(), 146 | block_hash: eth_block.hash.unwrap_or_default(), 147 | state_root: eth_block.state_root, 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /prover/src/circuits.rs: -------------------------------------------------------------------------------- 1 | use crate::circuit_witness::CircuitWitness; 2 | use halo2_proofs::halo2curves::bn256::Fr; 3 | use rand::Rng; 4 | use zkevm_circuits::bytecode_circuit::bytecode_unroller::BytecodeCircuit; 5 | use zkevm_circuits::copy_circuit::CopyCircuit; 6 | use zkevm_circuits::evm_circuit::EvmCircuit; 7 | use zkevm_circuits::exp_circuit::ExpCircuit; 8 | use zkevm_circuits::keccak_circuit::keccak_packed_multi::KeccakCircuit; 9 | use zkevm_circuits::pi_circuit::PiCircuit; 10 | use zkevm_circuits::pi_circuit::PiTestCircuit; 11 | use zkevm_circuits::state_circuit::StateCircuit; 12 | use zkevm_circuits::super_circuit::SuperCircuit; 13 | use zkevm_circuits::tx_circuit::TxCircuit; 14 | use zkevm_circuits::util::SubCircuit; 15 | 16 | /// Returns a instance of the `SuperCircuit`. 17 | pub fn gen_super_circuit< 18 | const MAX_TXS: usize, 19 | const MAX_CALLDATA: usize, 20 | const MAX_RWS: usize, 21 | RNG: Rng, 22 | >( 23 | witness: &CircuitWitness, 24 | mut _rng: RNG, 25 | ) -> Result, String> { 26 | let block = witness.evm_witness(); 27 | 28 | let evm_circuit = EvmCircuit::new_from_block(&block); 29 | let state_circuit = StateCircuit::new_from_block(&block); 30 | let tx_circuit = TxCircuit::new_from_block(&block); 31 | let pi_circuit = PiCircuit::new_from_block(&block); 32 | let bytecode_circuit = BytecodeCircuit::new_from_block(&block); 33 | let copy_circuit = CopyCircuit::new_from_block(&block); 34 | let exp_circuit = ExpCircuit::new_from_block(&block); 35 | let keccak_circuit = KeccakCircuit::new_from_block(&block); 36 | let circuit = SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_RWS> { 37 | evm_circuit, 38 | state_circuit, 39 | tx_circuit, 40 | pi_circuit, 41 | bytecode_circuit, 42 | copy_circuit, 43 | exp_circuit, 44 | keccak_circuit, 45 | }; 46 | 47 | Ok(circuit) 48 | } 49 | 50 | /// Returns a instance of the `PiTestCircuit`. 51 | pub fn gen_pi_circuit< 52 | const MAX_TXS: usize, 53 | const MAX_CALLDATA: usize, 54 | const MAX_RWS: usize, 55 | RNG: Rng, 56 | >( 57 | witness: &CircuitWitness, 58 | mut _rng: RNG, 59 | ) -> Result, String> { 60 | let block = witness.evm_witness(); 61 | let circuit = PiTestCircuit::(PiCircuit::new_from_block(&block)); 62 | 63 | Ok(circuit) 64 | } 65 | 66 | /// Returns a instance of the `EvmCircuit`. 67 | pub fn gen_evm_circuit< 68 | const MAX_TXS: usize, 69 | const MAX_CALLDATA: usize, 70 | const MAX_RWS: usize, 71 | RNG: Rng, 72 | >( 73 | witness: &CircuitWitness, 74 | mut _rng: RNG, 75 | ) -> Result, String> { 76 | let block = witness.evm_witness(); 77 | Ok(EvmCircuit::new_from_block(&block)) 78 | } 79 | 80 | /// Returns a instance of the `StateCircuit`. 81 | pub fn gen_state_circuit< 82 | const MAX_TXS: usize, 83 | const MAX_CALLDATA: usize, 84 | const MAX_RWS: usize, 85 | RNG: Rng, 86 | >( 87 | witness: &CircuitWitness, 88 | mut _rng: RNG, 89 | ) -> Result, String> { 90 | let block = witness.evm_witness(); 91 | Ok(StateCircuit::new_from_block(&block)) 92 | } 93 | 94 | /// Returns a instance of the `TxCircuit`. 95 | pub fn gen_tx_circuit< 96 | const MAX_TXS: usize, 97 | const MAX_CALLDATA: usize, 98 | const MAX_RWS: usize, 99 | RNG: Rng, 100 | >( 101 | witness: &CircuitWitness, 102 | mut _rng: RNG, 103 | ) -> Result, String> { 104 | let block = witness.evm_witness(); 105 | Ok(TxCircuit::new_from_block(&block)) 106 | } 107 | 108 | /// Returns a instance of the `BytecodeCircuit`. 109 | pub fn gen_bytecode_circuit< 110 | const MAX_TXS: usize, 111 | const MAX_CALLDATA: usize, 112 | const MAX_RWS: usize, 113 | RNG: Rng, 114 | >( 115 | witness: &CircuitWitness, 116 | mut _rng: RNG, 117 | ) -> Result, String> { 118 | let block = witness.evm_witness(); 119 | Ok(BytecodeCircuit::new_from_block(&block)) 120 | } 121 | 122 | /// Returns a instance of the `CopyCircuit`. 123 | pub fn gen_copy_circuit< 124 | const MAX_TXS: usize, 125 | const MAX_CALLDATA: usize, 126 | const MAX_RWS: usize, 127 | RNG: Rng, 128 | >( 129 | witness: &CircuitWitness, 130 | mut _rng: RNG, 131 | ) -> Result, String> { 132 | let block = witness.evm_witness(); 133 | Ok(CopyCircuit::new_from_block(&block)) 134 | } 135 | 136 | /// Returns a instance of the `ExpCircuit`. 137 | pub fn gen_exp_circuit< 138 | const MAX_TXS: usize, 139 | const MAX_CALLDATA: usize, 140 | const MAX_RWS: usize, 141 | RNG: Rng, 142 | >( 143 | witness: &CircuitWitness, 144 | mut _rng: RNG, 145 | ) -> Result, String> { 146 | let block = witness.evm_witness(); 147 | Ok(ExpCircuit::new_from_block(&block)) 148 | } 149 | 150 | /// Returns a instance of the `KeccakCircuit`. 151 | pub fn gen_keccak_circuit< 152 | const MAX_TXS: usize, 153 | const MAX_CALLDATA: usize, 154 | const MAX_RWS: usize, 155 | RNG: Rng, 156 | >( 157 | witness: &CircuitWitness, 158 | mut _rng: RNG, 159 | ) -> Result, String> { 160 | let block = witness.evm_witness(); 161 | Ok(KeccakCircuit::new_from_block(&block)) 162 | } 163 | -------------------------------------------------------------------------------- /prover/src/lib.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::halo2curves::bn256::Bn256; 2 | use halo2_proofs::halo2curves::bn256::G1Affine; 3 | use halo2_proofs::plonk::ProvingKey; 4 | use halo2_proofs::poly::kzg::commitment::KZGCommitmentScheme; 5 | use halo2_proofs::poly::kzg::commitment::ParamsKZG; 6 | 7 | pub type ProverParams = ParamsKZG; 8 | pub type ProverCommitmentScheme = KZGCommitmentScheme; 9 | pub type ProverKey = ProvingKey; 10 | 11 | pub mod aggregation_circuit; 12 | pub mod circuit_autogen; 13 | pub mod circuit_witness; 14 | pub mod circuits; 15 | pub mod server; 16 | pub mod shared_state; 17 | pub mod utils; 18 | -------------------------------------------------------------------------------- /prover/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::circuit_witness::CircuitWitness; 2 | use crate::shared_state::SharedState; 3 | use hyper::body::Buf; 4 | use hyper::body::HttpBody; 5 | use hyper::header::HeaderValue; 6 | use hyper::service::{make_service_fn, service_fn}; 7 | use hyper::{Body, Method, Request, Response, Server, StatusCode}; 8 | use zkevm_common::json_rpc::JsonRpcError; 9 | use zkevm_common::json_rpc::JsonRpcRequest; 10 | use zkevm_common::json_rpc::JsonRpcResponse; 11 | use zkevm_common::json_rpc::JsonRpcResponseError; 12 | use zkevm_common::prover::*; 13 | 14 | /// Starts the proverd json-rpc server. 15 | /// Note: the server may not immediately listening after returning the 16 | /// `JoinHandle`. 17 | pub fn serve(ctx: &SharedState, addr: &str) -> tokio::task::JoinHandle<()> { 18 | let addr = addr 19 | .parse::() 20 | .expect("valid socket address"); 21 | let ctx = ctx.clone(); 22 | tokio::spawn(async move { 23 | let service = make_service_fn(move |_| { 24 | let ctx = ctx.clone(); 25 | let service = service_fn(move |req| handle_request(ctx.clone(), req)); 26 | 27 | async move { Ok::<_, hyper::Error>(service) } 28 | }); 29 | let server = Server::bind(&addr).serve(service); 30 | log::info!("Listening on http://{}", addr); 31 | server.await.expect("server should be serving"); 32 | }) 33 | } 34 | 35 | /// sets default headers for CORS requests 36 | fn set_headers(headers: &mut hyper::HeaderMap, extended: bool) { 37 | headers.insert("content-type", HeaderValue::from_static("application/json")); 38 | headers.insert("access-control-allow-origin", HeaderValue::from_static("*")); 39 | 40 | if extended { 41 | headers.insert( 42 | "access-control-allow-methods", 43 | HeaderValue::from_static("post, get, options"), 44 | ); 45 | headers.insert( 46 | "access-control-allow-headers", 47 | HeaderValue::from_static("origin, content-type, accept, x-requested-with"), 48 | ); 49 | headers.insert("access-control-max-age", HeaderValue::from_static("300")); 50 | } 51 | } 52 | 53 | async fn handle_request( 54 | shared_state: SharedState, 55 | req: Request, 56 | ) -> Result, hyper::Error> { 57 | { 58 | // limits the request size 59 | const MAX_BODY_SIZE: u64 = 1 << 20; 60 | let response_content_length = match req.body().size_hint().upper() { 61 | Some(v) => v, 62 | None => MAX_BODY_SIZE + 1, 63 | }; 64 | 65 | if response_content_length > MAX_BODY_SIZE { 66 | let mut resp = Response::new(Body::from("request too large")); 67 | *resp.status_mut() = StatusCode::BAD_REQUEST; 68 | return Ok(resp); 69 | } 70 | } 71 | 72 | match (req.method(), req.uri().path()) { 73 | (&Method::GET, "/health") => { 74 | // nothing to report yet - healthy by default 75 | let mut resp = Response::default(); 76 | set_headers(resp.headers_mut(), false); 77 | Ok(resp) 78 | } 79 | 80 | // returns http 200 if busy else 204. 81 | // can be used programmatically for e.g. shutting down the instance if no workis being 82 | // done. 83 | (&Method::GET, "/status") => { 84 | let rw = shared_state.rw.lock().await; 85 | let is_busy = rw.pending.is_some() || rw.tasks.iter().any(|e| e.result.is_none()); 86 | drop(rw); 87 | 88 | let mut resp = Response::default(); 89 | *resp.status_mut() = match is_busy { 90 | false => StatusCode::NO_CONTENT, 91 | true => StatusCode::OK, 92 | }; 93 | set_headers(resp.headers_mut(), false); 94 | Ok(resp) 95 | } 96 | 97 | // json-rpc 98 | (&Method::POST, "/") => { 99 | let body_bytes = hyper::body::aggregate(req.into_body()) 100 | .await 101 | .unwrap() 102 | .reader(); 103 | let json_req: Result>, serde_json::Error> = 104 | serde_json::from_reader(body_bytes); 105 | 106 | if let Err(err) = json_req { 107 | let payload = serde_json::to_vec(&JsonRpcResponseError { 108 | jsonrpc: "2.0".to_string(), 109 | id: 0.into(), 110 | error: JsonRpcError { 111 | // parser error 112 | code: -32700, 113 | message: err.to_string(), 114 | }, 115 | }) 116 | .unwrap(); 117 | let mut resp = Response::new(Body::from(payload)); 118 | set_headers(resp.headers_mut(), false); 119 | return Ok(resp); 120 | } 121 | 122 | let json_req = json_req.unwrap(); 123 | let result: Result = 124 | handle_method(json_req.method.as_str(), &json_req.params, &shared_state).await; 125 | let payload = match result { 126 | Err(err) => { 127 | serde_json::to_vec(&JsonRpcResponseError { 128 | jsonrpc: "2.0".to_string(), 129 | id: json_req.id, 130 | error: JsonRpcError { 131 | // internal server error 132 | code: -32000, 133 | message: err, 134 | }, 135 | }) 136 | } 137 | Ok(val) => serde_json::to_vec(&JsonRpcResponse { 138 | jsonrpc: "2.0".to_string(), 139 | id: json_req.id, 140 | result: Some(val), 141 | }), 142 | }; 143 | let mut resp = Response::new(Body::from(payload.unwrap())); 144 | set_headers(resp.headers_mut(), false); 145 | Ok(resp) 146 | } 147 | 148 | // serve CORS headers 149 | (&Method::OPTIONS, "/") => { 150 | let mut resp = Response::default(); 151 | set_headers(resp.headers_mut(), true); 152 | Ok(resp) 153 | } 154 | 155 | // everything else 156 | _ => { 157 | let mut not_found = Response::default(); 158 | *not_found.status_mut() = StatusCode::NOT_FOUND; 159 | Ok(not_found) 160 | } 161 | } 162 | } 163 | 164 | async fn handle_method( 165 | method: &str, 166 | params: &[serde_json::Value], 167 | shared_state: &SharedState, 168 | ) -> Result { 169 | match method { 170 | // enqueues a task for computating proof for any given block 171 | "proof" => { 172 | let options = params.get(0).ok_or("expected struct ProofRequestOptions")?; 173 | let options: ProofRequestOptions = 174 | serde_json::from_value(options.to_owned()).map_err(|e| e.to_string())?; 175 | 176 | shared_state 177 | .get_or_enqueue(&options) 178 | .await 179 | .map(|result| serde_json::to_value(result?).map_err(|e| e.to_string())) 180 | .unwrap_or_else(|| Ok(serde_json::Value::Null)) 181 | } 182 | 183 | "circuit_config" => { 184 | let options = params.get(0).ok_or("expected struct ProofRequestOptions")?; 185 | let options: ProofRequestOptions = 186 | serde_json::from_value(options.to_owned()).map_err(|e| e.to_string())?; 187 | 188 | let witness = CircuitWitness::from_rpc(&options.block, &options.rpc) 189 | .await 190 | .map_err(|e| e.to_string())?; 191 | 192 | let circuit_config = 193 | crate::match_circuit_params!(witness.gas_used(), CIRCUIT_CONFIG, { 194 | return Err(format!( 195 | "No circuit parameters found for block with gas={}", 196 | witness.gas_used() 197 | )); 198 | }); 199 | 200 | Ok(serde_json::to_value(circuit_config).unwrap()) 201 | } 202 | 203 | // TODO: Add the abilitity to abort the current task. 204 | 205 | // returns `NodeInformation` 206 | // used internally for p2p communication 207 | "info" => Ok(serde_json::to_value(shared_state.get_node_information().await).unwrap()), 208 | 209 | // returns `NodeStatus` 210 | // used internally for p2p communication 211 | "status" => { 212 | let mut pending_task = None; 213 | let rw = shared_state.rw.lock().await; 214 | 215 | if let Some(val) = &rw.pending { 216 | pending_task = Some(val.clone()); 217 | } 218 | 219 | let ret = NodeStatus { 220 | id: shared_state.ro.node_id.clone(), 221 | task: pending_task, 222 | obtained: rw.obtained, 223 | }; 224 | drop(rw); 225 | 226 | Ok(serde_json::to_value(ret).unwrap()) 227 | } 228 | 229 | // Note: this only flushes `this` instance and not any other nodes. 230 | "flush" => { 231 | #[derive(serde::Deserialize)] 232 | struct FlushRequestOptions { 233 | cache: bool, 234 | pending: bool, 235 | completed: bool, 236 | } 237 | 238 | let options = params.get(0).ok_or("expected struct FlushRequestOptions")?; 239 | let options: FlushRequestOptions = 240 | serde_json::from_value(options.to_owned()).map_err(|e| e.to_string())?; 241 | let mut rw_state = shared_state.rw.lock().await; 242 | 243 | if options.cache { 244 | rw_state.pk_cache.clear(); 245 | } 246 | if options.pending { 247 | rw_state.tasks.retain(|e| e.result.is_some()); 248 | } 249 | if options.completed { 250 | rw_state.tasks.retain(|e| e.result.is_none()); 251 | } 252 | 253 | Ok(serde_json::Value::Bool(true)) 254 | } 255 | 256 | // TODO: remove these obsolete methods later. 257 | // the following methods can be used to programmatically 258 | // prune the `tasks` from the list. 259 | "flushAll" => { 260 | shared_state.rw.lock().await.tasks.clear(); 261 | Ok(serde_json::Value::Bool(true)) 262 | } 263 | "flushPending" => { 264 | shared_state 265 | .rw 266 | .lock() 267 | .await 268 | .tasks 269 | .retain(|e| e.result.is_some()); 270 | Ok(serde_json::Value::Bool(true)) 271 | } 272 | "flushCompleted" => { 273 | shared_state 274 | .rw 275 | .lock() 276 | .await 277 | .tasks 278 | .retain(|e| e.result.is_none()); 279 | Ok(serde_json::Value::Bool(true)) 280 | } 281 | _ => Err("this method is not available".to_string()), 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /prover/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::G1Affine; 2 | use crate::ProverCommitmentScheme; 3 | use crate::ProverKey; 4 | use crate::ProverParams; 5 | use eth_types::U256; 6 | use halo2_proofs::dev::MockProver; 7 | use halo2_proofs::halo2curves::bn256::Fr; 8 | use halo2_proofs::plonk::create_proof; 9 | use halo2_proofs::plonk::verify_proof; 10 | use halo2_proofs::plonk::Circuit; 11 | use halo2_proofs::poly::commitment::Params; 12 | use halo2_proofs::poly::commitment::ParamsProver; 13 | use halo2_proofs::poly::kzg::multiopen::ProverGWC; 14 | use halo2_proofs::poly::kzg::multiopen::VerifierGWC; 15 | use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; 16 | use halo2_proofs::transcript::EncodedChallenge; 17 | use halo2_proofs::transcript::TranscriptReadBuffer; 18 | use halo2_proofs::transcript::TranscriptWriterBuffer; 19 | use rand::rngs::StdRng; 20 | use rand::Rng; 21 | use zkevm_circuits::tx_circuit::PrimeField; 22 | 23 | use rand::SeedableRng; 24 | use std::clone::Clone; 25 | use std::io::Cursor; 26 | 27 | /// Returns [, ...] of `instance` 28 | pub fn gen_num_instance(instance: &[Vec]) -> Vec { 29 | instance.iter().map(|v| v.len()).collect() 30 | } 31 | 32 | /// Returns the finalized transcript. 33 | /// Runs the MockProver on `create_proof` error and panics afterwards. 34 | pub fn gen_proof< 35 | C: Circuit + Clone, 36 | E: EncodedChallenge, 37 | TR: TranscriptReadBuffer>, G1Affine, E>, 38 | TW: TranscriptWriterBuffer, G1Affine, E>, 39 | RNG: Rng, 40 | >( 41 | params: &ProverParams, 42 | pk: &ProverKey, 43 | circuit: C, 44 | instance: Vec>, 45 | rng: RNG, 46 | mock_feedback: bool, 47 | verify: bool, 48 | ) -> Vec { 49 | let mut transcript = TW::init(Vec::new()); 50 | let inputs: Vec<&[Fr]> = instance.iter().map(|v| v.as_slice()).collect(); 51 | let res = create_proof::, _, _, TW, _>( 52 | params, 53 | pk, 54 | &[circuit.clone()], 55 | &[inputs.as_slice()], 56 | rng, 57 | &mut transcript, 58 | ); 59 | // run the `MockProver` and return (hopefully) useful errors 60 | if let Err(proof_err) = res { 61 | if mock_feedback { 62 | let res = MockProver::run(params.k(), &circuit, instance) 63 | .expect("MockProver::run") 64 | .verify_par(); 65 | panic!("gen_proof: {:#?}\nMockProver: {:#?}", proof_err, res); 66 | } else { 67 | panic!("gen_proof: {:#?}", proof_err); 68 | } 69 | } 70 | 71 | let proof = transcript.finalize(); 72 | if verify { 73 | let mut transcript = TR::init(Cursor::new(proof.clone())); 74 | let res = verify_proof::<_, VerifierGWC<_>, _, TR, _>( 75 | params.verifier_params(), 76 | pk.get_vk(), 77 | AccumulatorStrategy::new(params.verifier_params()), 78 | &[inputs.as_slice()], 79 | &mut transcript, 80 | ); 81 | 82 | if let Err(verify_err) = res { 83 | panic!("verify_proof: {:#?}", verify_err); 84 | } 85 | } 86 | 87 | proof 88 | } 89 | 90 | /// Fixed rng for testing purposes 91 | pub fn fixed_rng() -> StdRng { 92 | StdRng::seed_from_u64(9) 93 | } 94 | 95 | /// Collect circuit instance as flat vector 96 | pub fn collect_instance(instance: &[Vec]) -> Vec { 97 | instance 98 | .iter() 99 | .flatten() 100 | .map(|v| U256::from_little_endian(v.to_repr().as_ref())) 101 | .collect() 102 | } 103 | -------------------------------------------------------------------------------- /prover/tests/proverd.rs: -------------------------------------------------------------------------------- 1 | use prover::server::serve; 2 | use prover::shared_state::SharedState; 3 | use tokio::time::{sleep, Duration}; 4 | use zkevm_common::prover::*; 5 | 6 | fn init_logger() { 7 | let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")) 8 | .is_test(true) 9 | .try_init(); 10 | } 11 | 12 | #[tokio::test] 13 | async fn proverd_simple_signaling() { 14 | init_logger(); 15 | 16 | let node_a = SharedState::new("a".to_string(), Some("127.0.0.1:11111".to_string())); 17 | let node_b = SharedState::new("b".to_string(), Some("127.0.0.1:11112".to_string())); 18 | // start http servers 19 | { 20 | let _ = serve(&node_a, node_b.ro.node_lookup.as_ref().unwrap()); 21 | let _ = serve(&node_b, node_a.ro.node_lookup.as_ref().unwrap()); 22 | } 23 | 24 | // wait a bit for the rpc server to start 25 | sleep(Duration::from_millis(300)).await; 26 | 27 | let proof_a = ProofRequestOptions { 28 | circuit: "super".to_string(), 29 | block: 1, 30 | retry: false, 31 | rpc: "http://localhost:1111".to_string(), 32 | ..Default::default() 33 | }; 34 | let proof_b = ProofRequestOptions { 35 | circuit: "super".to_string(), 36 | block: 2, 37 | retry: false, 38 | rpc: "http://localhost:1111".to_string(), 39 | ..Default::default() 40 | }; 41 | 42 | // enqueue tasks 43 | assert!(node_a.get_or_enqueue(&proof_a).await.is_none()); 44 | assert!(node_b.get_or_enqueue(&proof_b).await.is_none()); 45 | 46 | // start work on node_a 47 | node_a.duty_cycle().await; 48 | assert!(node_a.get_or_enqueue(&proof_a).await.is_some()); 49 | 50 | // node_b didn't sync yet 51 | assert!(node_b.get_or_enqueue(&proof_a).await.is_none()); 52 | // sync, do work 53 | let _ = node_b.merge_tasks_from_peers().await; 54 | // check again 55 | assert!(node_b.get_or_enqueue(&proof_a).await.is_some()); 56 | 57 | // no result yet 58 | assert!(node_b.get_or_enqueue(&proof_b).await.is_none()); 59 | // sync, do work 60 | node_b.duty_cycle().await; 61 | // check again 62 | assert!(node_b.get_or_enqueue(&proof_b).await.is_some()); 63 | 64 | // node_a didn't sync yet 65 | assert!(node_a.get_or_enqueue(&proof_b).await.is_none()); 66 | // sync node_a 67 | let _ = node_a.merge_tasks_from_peers().await; 68 | // check again 69 | assert!(node_a.get_or_enqueue(&proof_b).await.is_some()); 70 | } 71 | -------------------------------------------------------------------------------- /prover/tests/verifier.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "autogen")] 2 | 3 | use eth_types::Address; 4 | use eth_types::Bytes; 5 | use halo2_proofs::arithmetic::Field; 6 | use halo2_proofs::halo2curves::bn256::{Fq, Fr, G1Affine}; 7 | use halo2_proofs::plonk::keygen_pk; 8 | use halo2_proofs::plonk::keygen_vk; 9 | use halo2_proofs::plonk::VerifyingKey; 10 | use halo2_proofs::poly::commitment::ParamsProver; 11 | use plonk_verifier::cost::CostEstimation; 12 | use plonk_verifier::loader::evm::EvmLoader; 13 | use plonk_verifier::loader::native::NativeLoader; 14 | use plonk_verifier::util::transcript::TranscriptWrite; 15 | use plonk_verifier::verifier::PlonkProof; 16 | use plonk_verifier::{ 17 | system::halo2::{compile, transcript::evm::EvmTranscript, Config}, 18 | verifier::PlonkVerifier, 19 | }; 20 | use prover::aggregation_circuit::AggregationCircuit; 21 | use prover::aggregation_circuit::Pcs; 22 | use prover::aggregation_circuit::Plonk; 23 | use prover::aggregation_circuit::PoseidonTranscript; 24 | use prover::aggregation_circuit::Snark; 25 | use prover::circuit_witness::CircuitWitness; 26 | use prover::circuits::*; 27 | use prover::utils::fixed_rng; 28 | use prover::utils::gen_num_instance; 29 | use prover::utils::gen_proof; 30 | use prover::ProverParams; 31 | use rand::rngs::OsRng; 32 | use std::env::var; 33 | use std::fs; 34 | use std::io::Write; 35 | use std::rc::Rc; 36 | use zkevm_common::prover::*; 37 | 38 | #[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)] 39 | struct Verifier { 40 | label: String, 41 | config: CircuitConfig, 42 | runtime_code: Bytes, 43 | address: Address, 44 | } 45 | 46 | impl Verifier { 47 | fn build(&mut self) -> &Self { 48 | let mut tmp = [0; 20]; 49 | let bytes = self.label.as_bytes(); 50 | let x = 20 - bytes.len(); 51 | for (i, v) in bytes.iter().enumerate() { 52 | tmp[i + x] = *v; 53 | } 54 | self.address = Address::from(tmp); 55 | 56 | self 57 | } 58 | } 59 | 60 | fn write_bytes(name: &str, vec: &[u8]) { 61 | let dir = "./../build/plonk-verifier"; 62 | fs::create_dir_all(dir).unwrap_or_else(|_| panic!("create {}", dir)); 63 | let path = format!("{}/{}", dir, name); 64 | fs::File::create(&path) 65 | .unwrap_or_else(|_| panic!("create {}", &path)) 66 | .write_all(vec) 67 | .unwrap_or_else(|_| panic!("write {}", &path)); 68 | } 69 | 70 | fn gen_verifier(params: &ProverParams, vk: &VerifyingKey, config: Config) -> Vec { 71 | let num_instance = config.num_instance.clone(); 72 | let svk = params.get_g()[0].into(); 73 | let dk = (params.g2(), params.s_g2()).into(); 74 | let protocol = compile(params, vk, config); 75 | 76 | let loader = EvmLoader::new::(); 77 | let protocol = protocol.loaded(&loader); 78 | let mut transcript = EvmTranscript::<_, Rc, _, _>::new(&loader); 79 | 80 | let instances = transcript.load_instances(num_instance); 81 | let proof = Plonk::read_proof(&svk, &protocol, &instances, &mut transcript).unwrap(); 82 | Plonk::verify(&svk, &dk, &protocol, &instances, &proof).unwrap(); 83 | 84 | loader.runtime_code() 85 | } 86 | 87 | macro_rules! gen_match { 88 | ($LABEL:expr, $CIRCUIT:ident, $GAS:expr) => {{ 89 | let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) 90 | .try_init(); 91 | 92 | prover::match_circuit_params!( 93 | $GAS, 94 | { 95 | let snark = { 96 | let witness = CircuitWitness::dummy(CIRCUIT_CONFIG).unwrap(); 97 | let circuit = $CIRCUIT::< 98 | { CIRCUIT_CONFIG.max_txs }, 99 | { CIRCUIT_CONFIG.max_calldata }, 100 | { CIRCUIT_CONFIG.max_rws }, 101 | _, 102 | >(&witness, fixed_rng()) 103 | .expect("gen_static_circuit"); 104 | let params = ProverParams::setup(CIRCUIT_CONFIG.min_k as u32, fixed_rng()); 105 | let vk = keygen_vk(¶ms, &circuit).expect("vk"); 106 | let instance = circuit.instance(); 107 | 108 | { 109 | let mut data = Verifier::default(); 110 | data.label = format!("{}-{}", $LABEL, CIRCUIT_CONFIG.block_gas_limit); 111 | data.config = CIRCUIT_CONFIG; 112 | data.runtime_code = gen_verifier( 113 | ¶ms, 114 | &vk, 115 | Config::kzg().with_num_instance(gen_num_instance(&instance)), 116 | ) 117 | .into(); 118 | 119 | if var("ONLY_EVM").is_ok() { 120 | log::info!("returning early"); 121 | let data = data.build(); 122 | write_bytes(&data.label, &serde_json::to_vec(data).unwrap()); 123 | return; 124 | } 125 | 126 | let data = data.build(); 127 | write_bytes(&data.label, &serde_json::to_vec(data).unwrap()); 128 | } 129 | 130 | let protocol = compile( 131 | ¶ms, 132 | &vk, 133 | Config::kzg().with_num_instance(gen_num_instance(&instance)), 134 | ); 135 | 136 | let proof = if var("FAST").is_ok() { 137 | let mut transcript = PoseidonTranscript::::new(Vec::new()); 138 | 139 | for _ in 0..protocol 140 | .num_witness 141 | .iter() 142 | .chain(Some(&protocol.quotient.num_chunk())) 143 | .sum::() 144 | { 145 | transcript.write_ec_point(G1Affine::random(OsRng)).unwrap(); 146 | } 147 | 148 | for _ in 0..protocol.evaluations.len() { 149 | transcript.write_scalar(Fr::random(OsRng)).unwrap(); 150 | } 151 | 152 | let queries = 153 | PlonkProof::::empty_queries(&protocol); 154 | let estimate = Pcs::estimate_cost(&queries); 155 | for _ in 0..estimate.num_commitment { 156 | transcript.write_ec_point(G1Affine::random(OsRng)).unwrap(); 157 | } 158 | log::info!( 159 | "{} {:#?} num_witness={} evalutations={} estimate={:#?}", 160 | $LABEL, 161 | CIRCUIT_CONFIG, 162 | protocol.num_witness.len(), 163 | protocol.evaluations.len(), 164 | estimate 165 | ); 166 | 167 | transcript.finalize() 168 | } else { 169 | gen_proof::< 170 | _, 171 | _, 172 | PoseidonTranscript, 173 | PoseidonTranscript, 174 | _, 175 | >( 176 | ¶ms, 177 | &keygen_pk(¶ms, vk, &circuit).expect("pk"), 178 | circuit, 179 | instance.clone(), 180 | fixed_rng(), 181 | true, 182 | true, 183 | ) 184 | }; 185 | 186 | Snark::new(protocol, instance, proof) 187 | }; 188 | 189 | let agg_params = 190 | ProverParams::setup(CIRCUIT_CONFIG.min_k_aggregation as u32, fixed_rng()); 191 | let agg_circuit = AggregationCircuit::new(&agg_params, [snark], fixed_rng()); 192 | let agg_vk = keygen_vk(&agg_params, &agg_circuit).expect("vk"); 193 | 194 | let mut data = Verifier::default(); 195 | data.label = format!("{}-{}-a", $LABEL, CIRCUIT_CONFIG.block_gas_limit); 196 | data.config = CIRCUIT_CONFIG; 197 | data.runtime_code = gen_verifier( 198 | &agg_params, 199 | &agg_vk, 200 | Config::kzg() 201 | .with_num_instance(AggregationCircuit::num_instance()) 202 | .with_accumulator_indices(Some(AggregationCircuit::accumulator_indices())), 203 | ) 204 | .into(); 205 | 206 | let data = data.build(); 207 | write_bytes(&data.label, &serde_json::to_vec(data).unwrap()); 208 | }, 209 | { 210 | panic!("no circuit parameters found"); 211 | } 212 | ); 213 | }}; 214 | } 215 | 216 | // wrapper 217 | macro_rules! gen { 218 | ($LABEL:expr, $CIRCUIT:ident, $GAS:expr) => {{ 219 | fn func() { 220 | gen_match!($LABEL, $CIRCUIT, $GAS); 221 | } 222 | func(); 223 | }}; 224 | } 225 | 226 | macro_rules! for_each { 227 | ($LABEL:expr, $CIRCUIT:ident) => {{ 228 | gen!($LABEL, $CIRCUIT, 63_000); 229 | gen!($LABEL, $CIRCUIT, 300_000); 230 | }}; 231 | } 232 | 233 | #[test] 234 | fn autogen_verifier_super() { 235 | for_each!("super", gen_super_circuit); 236 | } 237 | 238 | #[test] 239 | fn autogen_verifier_pi() { 240 | for_each!("pi", gen_pi_circuit); 241 | } 242 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2022-09-17 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | edition = "2021" 3 | -------------------------------------------------------------------------------- /scripts/autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | cargo test --release --features autogen -- --nocapture $@ 6 | -------------------------------------------------------------------------------- /scripts/ci_autogen_commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | if [ -z "$CI" ]; then 6 | exit 1 7 | fi 8 | 9 | git config user.email 'github-actions@github.com' 10 | git config user.name github-actions 11 | git commit -am 'updates from autogen workflow' || exit 0 12 | git push 13 | -------------------------------------------------------------------------------- /scripts/ci_commit_errors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | if [ ! -d errors/ ]; then 6 | exit 0 7 | fi 8 | 9 | if [ -z "$CI" ]; then 10 | exit 1 11 | fi 12 | 13 | sudo chown -R $(id -nu) errors/ 14 | 15 | git config user.email 'github-actions@github.com' 16 | git config user.name github-actions 17 | name=${1:-unknown} 18 | branch=prover-error-"$1"-$(git rev-parse HEAD) 19 | git checkout -b $branch && git add errors/ && git commit -m 'add prover errors' && git push origin $branch 20 | 21 | git reset --hard HEAD^ 22 | # exit with error to signal prover failure 23 | exit 1 24 | -------------------------------------------------------------------------------- /scripts/compile_contracts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ROOT="$(dirname $0)/.." 4 | 5 | for file in $(find "$ROOT"/contracts/templates/ -iname '*.sol'); do 6 | name=$(basename $file) 7 | generated="$ROOT"/contracts/generated/$name 8 | cp "$file" "$generated" 9 | for inc in $(cat "$file" | grep "//@INCLUDE:"); do 10 | template=$(echo "$inc" | awk -F":" '{print $NF}') 11 | sed -i -e "\#$inc#r $ROOT/contracts/templates/$template" "$generated" 12 | done 13 | done 14 | 15 | OUTPUT_PATH="$ROOT/build/contracts" 16 | mkdir -p "$OUTPUT_PATH" 17 | 18 | SOLC=$(which solc || printf '%s' "docker run --rm -w /app -v $(pwd):/app ethereum/solc:0.8.16") 19 | $SOLC \ 20 | --metadata-hash none \ 21 | --combined-json bin,bin-runtime,srcmap,srcmap-runtime,storage-layout \ 22 | --optimize \ 23 | --optimize-runs 4294967295 \ 24 | --overwrite \ 25 | -o "$OUTPUT_PATH" \ 26 | $(find "$ROOT"/contracts/ -iname '*.sol' | grep -v templates/) 27 | -------------------------------------------------------------------------------- /scripts/dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | DOCKER_BUILDKIT=1 docker compose up -d dev 6 | docker compose exec dev bash 7 | -------------------------------------------------------------------------------- /scripts/get_block_fixtures.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # usage rpc_url block_number 3 | url=$1 4 | blockNumber=$2 5 | 6 | set -e 7 | 8 | rpc() { 9 | curl \ 10 | --silent \ 11 | -H 'content-type: application/json' \ 12 | -d '{"id":0, "jsonrpc":"2.0","method":"'$1'", "params":['$2']}' \ 13 | $url 14 | } 15 | 16 | hex() { 17 | printf '0x%x' $1 18 | } 19 | 20 | blockHex=$(hex $blockNumber) 21 | 22 | echo '### block.json' 23 | rpc 'eth_getBlockByNumber' '"'$blockHex'",true' | jq '.result' > block.json 24 | 25 | echo '### prestate.json' 26 | rpc 'debug_traceBlockByNumber' '"'$blockHex'",{"tracer":"prestateTracer"}' | jq '.result' > prestate.json 27 | 28 | echo '### block_hashes.json' 29 | stop=$(($blockNumber - 1)) 30 | start=$(($stop - 255)) 31 | tmp='[' 32 | for num in $(seq $start $stop); do 33 | if [ $num -lt 0 ]; then 34 | tmp=$tmp'"0x0000000000000000000000000000000000000000000000000000000000000000",' 35 | continue 36 | fi 37 | blockHex=$(hex $num) 38 | hash=$(rpc 'eth_getHeaderByNumber' '"'$blockHex'"' | jq '.result.hash') 39 | tmp=$tmp$hash',' 40 | done 41 | tmp=$(printf -- $tmp | head -c -1) 42 | tmp="$tmp]" 43 | echo "$tmp" | jq > block_hashes.json 44 | 45 | -------------------------------------------------------------------------------- /scripts/heavy_ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | rm -rf errors 6 | pkill -9 prover_rpcd || true 7 | 8 | trap 'pkill --parent $$' TERM EXIT INT 9 | 10 | ./scripts/compile_contracts.sh 11 | 12 | cargo build --release --bin prover_rpcd 13 | env time --output PROVER_STATS.txt --verbose -- \ 14 | cargo run --release --bin prover_rpcd 2>&1 | xz > PROVER_LOG.txt.xz & 15 | PID=$! 16 | perf stat --pid $PID -I 300000 -o PROVER_PERF.txt \ 17 | -e stalled-cycles-backend \ 18 | -e stalled-cycles-frontend \ 19 | -e instructions \ 20 | -e branch-instructions \ 21 | -e ic_fetch_stall.ic_stall_any \ 22 | -e ic_fetch_stall.ic_stall_back_pressure \ 23 | -e ic_fetch_stall.ic_stall_dq_empty \ 24 | -e sse_avx_stalls \ 25 | -e all_data_cache_accesses \ 26 | -e all_tlbs_flushed \ 27 | -e l1_data_cache_fills_all \ 28 | -e fp_ret_sse_avx_ops.all \ 29 | -e l1_data_cache_fills_all \ 30 | -e l2_cache_accesses_from_dc_misses \ 31 | -e l2_cache_accesses_from_ic_misses \ 32 | -e ic_tag_hit_miss.all_instruction_cache_accesses \ 33 | -e ic_tag_hit_miss.instruction_cache_hit \ 34 | -e ic_tag_hit_miss.instruction_cache_miss & 35 | 36 | # sleep a bit in case the geth nodes are not up yet 37 | sleep 3 38 | 39 | # finalize any leftover blocks 40 | COORDINATOR_DUMMY_PROVER=true cargo test -p coordinator -- finalize_chain --ignored || exit 1 41 | 42 | # now run all default tests 43 | COORDINATOR_DUMMY_PROVER=false cargo test -p coordinator -- $@ 44 | status=$? 45 | FAILED_BLOCKS=$(./scripts/rpc_prover.sh info | jq -cr '.result.tasks | map(select(.result.Err)) | map(.options.block) | .[]') 46 | 47 | pkill -9 prover_rpcd || true 48 | wait $PID 49 | cat PROVER_STATS.txt 50 | cat PROVER_PERF.txt 51 | 52 | if [ $status -eq 0 ]; then 53 | exit 0 54 | fi 55 | 56 | # if there are not failed proof requests, then something else failed 57 | if [ "${FAILED_BLOCKS}" = "" ]; then 58 | exit 1 59 | fi 60 | 61 | # error collection 62 | mkdir errors 63 | 64 | for block_num in $FAILED_BLOCKS; do 65 | ./scripts/get_block_fixtures.sh $COORDINATOR_L2_RPC_URL $block_num 66 | mkdir -p errors/$block_num 67 | mv block_hashes.json block.json prestate.json errors/$block_num/ 68 | done 69 | 70 | mv PROVER_LOG.txt.xz errors/ 71 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cargo fmt --all 4 | cargo clippy --all-features --all-targets 5 | -------------------------------------------------------------------------------- /scripts/patch_genesis.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'fs'; 4 | 5 | function pad (n, v) { 6 | return v.toString(16).padStart(n, '0'); 7 | } 8 | 9 | function getCode (name) { 10 | for (const id in artifacts) { 11 | if (id.split(':')[1] === name) { 12 | return '0x' + artifacts[id]['bin-runtime']; 13 | } 14 | } 15 | 16 | throw new Error(`${name} not found`); 17 | } 18 | 19 | const artifacts = JSON.parse(fs.readFileSync('./build/contracts/combined.json')).contracts; 20 | const L1_CONTRACTS = { 21 | '936a70c0b28532aa22240dce21f89a8399d6ac60': 'ZkEvmL1Bridge', 22 | '936a70c0b28532aa22240dce21f89a8399d6ac61': 'L1OptimismBridge', 23 | }; 24 | const baseAddress = BigInt('0x1111111111111111111111111111111111111111'); 25 | const path = './build/plonk-verifier'; 26 | if (fs.existsSync(path)) { 27 | for (const file of fs.readdirSync(path)) { 28 | const json = JSON.parse(fs.readFileSync(`${path}/${file}`)); 29 | const addr = pad(40, BigInt(json.address)); 30 | console.log({file, addr}); 31 | if (L1_CONTRACTS[addr]) { 32 | throw Error('exists'); 33 | } 34 | L1_CONTRACTS[addr] = { name: file, code: json.runtime_code }; 35 | } 36 | } 37 | 38 | const L2_CONTRACTS = { 39 | '0000000000000000000000000000000000010000': 'ZkEvmL2MessageDeliverer', 40 | '0000000000000000000000000000000000020000': 'ZkEvmL2MessageDispatcher', 41 | '4200000000000000000000000000000000000007': 'L2OptimisimBridge', 42 | }; 43 | const L1_TEMPLATE_PATH = 'docker/geth/templates/l1-testnet.json'; 44 | const L2_TEMPLATE_PATH = 'docker/geth/templates/l2-testnet.json'; 45 | 46 | const PROXY_CODE = getCode('Proxy'); 47 | const PROXY_SLOT = pad(64, BigInt.asUintN(256, '-1')); 48 | 49 | const OBJS = [ 50 | [L1_TEMPLATE_PATH, L1_CONTRACTS], 51 | [L2_TEMPLATE_PATH, L2_CONTRACTS], 52 | ]; 53 | 54 | for (const [path, contracts] of OBJS) { 55 | const genesis = JSON.parse(fs.readFileSync(path)); 56 | 57 | for (const addr in contracts) { 58 | let code; 59 | let name; 60 | let value = contracts[addr]; 61 | if (typeof value === 'string') { 62 | code = getCode(value); 63 | name = value; 64 | } else { 65 | code = value.code; 66 | name = value.name; 67 | } 68 | 69 | const proxy = genesis.alloc[addr] || { balance: '0' }; 70 | proxy.comment = 'Proxy:' + name; 71 | proxy.code = PROXY_CODE; 72 | proxy.storage = proxy.storage || {}; 73 | const implAddr = pad(40, BigInt.asUintN(160, BigInt('0x' + addr) + 0xcbaf2257000313ba2574n)); 74 | proxy.storage[PROXY_SLOT] = implAddr; 75 | genesis.alloc[addr] = proxy; 76 | 77 | genesis.alloc[implAddr] = { 78 | comment: name, 79 | balance: '0', 80 | code 81 | }; 82 | } 83 | 84 | fs.writeFileSync(path, JSON.stringify(genesis, null, 2)); 85 | } 86 | -------------------------------------------------------------------------------- /scripts/purge_chain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | docker compose down 6 | docker volume rm zkevm-chain_l1-testnet-geth zkevm-chain_leader-testnet-geth zkevm-chain_bootnode 7 | -------------------------------------------------------------------------------- /scripts/rpc_prover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | curl \ 6 | --silent \ 7 | -H 'content-type: application/json' \ 8 | -d '{"id":0, "jsonrpc":"2.0","method":"'$1'", "params":['$2']}' \ 9 | "$PROVERD_LOOKUP" 10 | -------------------------------------------------------------------------------- /scripts/test_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | COORDINATOR_L1_RPC_URL=http://coverage-l1:8545 6 | COORDINATOR_L2_RPC_URL=http://coverage-l2:8545 7 | 8 | curl -f "$COORDINATOR_L1_RPC_URL"/reload 9 | curl -f "$COORDINATOR_L2_RPC_URL"/reload 10 | 11 | cargo test "$@" 12 | 13 | curl -f "$COORDINATOR_L1_RPC_URL"/.lcov > build/coverage-report.lcov 14 | curl -f "$COORDINATOR_L2_RPC_URL"/.lcov >> build/coverage-report.lcov 15 | -------------------------------------------------------------------------------- /scripts/test_prover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | trap 'pkill --parent $$' TERM EXIT INT 6 | 7 | pkill -9 prover_rpcd || true 8 | 9 | cargo build --release --bin prover_rpcd 10 | perf record -e task-clock -F 1000 -g -- cargo run --release --bin prover_rpcd > PROVER_LOG.txt 2>&1 & 11 | COORDINATOR_DUMMY_PROVER=false cargo test -- $@ 12 | --------------------------------------------------------------------------------