mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-12 07:28:08 -05:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfc0fdd7ce | ||
|
|
426c57a5fa | ||
|
|
b7fdf48c30 | ||
|
|
ad0c918944 | ||
|
|
1098876183 | ||
|
|
9e520e7769 | ||
|
|
de7f6e56a9 | ||
|
|
3b323198dc | ||
|
|
c11e0283e8 | ||
|
|
a5a7844646 | ||
|
|
7ff5b190ec | ||
|
|
b297edd28d | ||
|
|
47c85d4983 | ||
|
|
1552e98b79 | ||
|
|
a65b3066a3 | ||
|
|
1f2b397bbd | ||
|
|
ae791a0714 | ||
|
|
c012f7132d | ||
|
|
6897cc54bd | ||
|
|
d21fa36803 | ||
|
|
fc75299eb3 | ||
|
|
4bfcd35d0c | ||
|
|
6d62f8e5fa | ||
|
|
392ae07736 | ||
|
|
db80b47820 | ||
|
|
daa1387208 | ||
|
|
67b05558e2 | ||
|
|
1e447b0fef | ||
|
|
f7c6ecadf4 | ||
|
|
9d94f943e5 | ||
|
|
de17ad43ff |
2
.github/workflows/common.yml
vendored
2
.github/workflows/common.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2025-02-14
|
||||
toolchain: nightly-2025-08-18
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Install Go
|
||||
|
||||
2
.github/workflows/coordinator.yml
vendored
2
.github/workflows/coordinator.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2025-02-14
|
||||
toolchain: nightly-2025-08-18
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Install Go
|
||||
|
||||
18
.github/workflows/docker.yml
vendored
18
.github/workflows/docker.yml
vendored
@@ -51,9 +51,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
rollup_relayer:
|
||||
runs-on:
|
||||
@@ -97,9 +95,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
blob_uploader:
|
||||
runs-on:
|
||||
@@ -143,9 +139,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
rollup-db-cli:
|
||||
runs-on:
|
||||
@@ -189,9 +183,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
bridgehistoryapi-fetcher:
|
||||
runs-on:
|
||||
@@ -235,9 +227,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
bridgehistoryapi-api:
|
||||
runs-on:
|
||||
@@ -281,9 +271,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
bridgehistoryapi-db-cli:
|
||||
runs-on:
|
||||
@@ -327,9 +315,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
coordinator-api:
|
||||
runs-on:
|
||||
@@ -372,9 +358,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
coordinator-cron:
|
||||
runs-on:
|
||||
@@ -418,6 +402,4 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
scrolltech/${{ env.REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
|
||||
${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest
|
||||
|
||||
9
.github/workflows/intermediate-docker.yml
vendored
9
.github/workflows/intermediate-docker.yml
vendored
@@ -22,11 +22,9 @@ on:
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- nightly-2023-12-03
|
||||
- nightly-2022-12-10
|
||||
- 1.86.0
|
||||
- nightly-2025-02-14
|
||||
default: "nightly-2023-12-03"
|
||||
- nightly-2025-08-18
|
||||
default: "nightly-2025-08-18"
|
||||
PYTHON_VERSION:
|
||||
description: "Python version"
|
||||
required: false
|
||||
@@ -41,7 +39,8 @@ on:
|
||||
options:
|
||||
- "11.7.1"
|
||||
- "12.2.2"
|
||||
default: "11.7.1"
|
||||
- "12.9.1"
|
||||
default: "12.9.1"
|
||||
CARGO_CHEF_TAG:
|
||||
description: "Cargo chef version"
|
||||
required: true
|
||||
|
||||
2010
Cargo.lock
generated
2010
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
44
Cargo.toml
44
Cargo.toml
@@ -14,15 +14,16 @@ edition = "2021"
|
||||
homepage = "https://scroll.io"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/scroll-tech/scroll"
|
||||
version = "4.5.8"
|
||||
version = "4.5.47"
|
||||
|
||||
[workspace.dependencies]
|
||||
scroll-zkvm-prover-euclid = { git = "https://github.com/scroll-tech/zkvm-prover", rev = "0dd7b19", package = "scroll-zkvm-prover" }
|
||||
scroll-zkvm-verifier-euclid = { git = "https://github.com/scroll-tech/zkvm-prover", rev = "0dd7b19", package = "scroll-zkvm-verifier" }
|
||||
scroll-zkvm-types = { git = "https://github.com/scroll-tech/zkvm-prover", rev = "0dd7b19" }
|
||||
scroll-zkvm-prover = { git = "https://github.com/scroll-tech/zkvm-prover", rev = "060be4c" }
|
||||
scroll-zkvm-verifier = { git = "https://github.com/scroll-tech/zkvm-prover", rev = "060be4c" }
|
||||
scroll-zkvm-types = { git = "https://github.com/scroll-tech/zkvm-prover", rev = "060be4c" }
|
||||
|
||||
sbv-primitives = { git = "https://github.com/scroll-tech/stateless-block-verifier", branch = "chore/upgrade", features = ["scroll"] }
|
||||
sbv-utils = { git = "https://github.com/scroll-tech/stateless-block-verifier", branch = "chore/upgrade" }
|
||||
sbv-primitives = { git = "https://github.com/scroll-tech/stateless-block-verifier", branch = "master", features = ["scroll", "rkyv"] }
|
||||
sbv-utils = { git = "https://github.com/scroll-tech/stateless-block-verifier", branch = "master" }
|
||||
sbv-core = { git = "https://github.com/scroll-tech/stateless-block-verifier", branch = "master", features = ["scroll"] }
|
||||
|
||||
metrics = "0.23.0"
|
||||
metrics-util = "0.17"
|
||||
@@ -30,7 +31,7 @@ metrics-tracing-context = "0.16.0"
|
||||
|
||||
anyhow = "1.0"
|
||||
alloy = { version = "1", default-features = false }
|
||||
alloy-primitives = { version = "1.2", default-features = false, features = ["tiny-keccak"] }
|
||||
alloy-primitives = { version = "1.3", default-features = false, features = ["tiny-keccak"] }
|
||||
# also use this to trigger "serde" feature for primitives
|
||||
alloy-serde = { version = "1", default-features = false }
|
||||
|
||||
@@ -46,23 +47,22 @@ once_cell = "1.20"
|
||||
base64 = "0.22"
|
||||
|
||||
[patch.crates-io]
|
||||
revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-bytecode = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-context = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-context-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-database = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-database-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-handler = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-inspector = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-interpreter = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-precompile = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-primitives = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm-state = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" }
|
||||
revm = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-bytecode = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-context = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-context-interface = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-database = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-database-interface = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-handler = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-inspector = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-interpreter = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-precompile = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-primitives = { git = "https://github.com/scroll-tech/revm" }
|
||||
revm-state = { git = "https://github.com/scroll-tech/revm" }
|
||||
|
||||
ruint = { git = "https://github.com/scroll-tech/uint.git", branch = "v1.15.0" }
|
||||
alloy-primitives = { git = "https://github.com/scroll-tech/alloy-core", branch = "v1.2.0" }
|
||||
alloy-primitives = { git = "https://github.com/scroll-tech/alloy-core", branch = "feat/rkyv" }
|
||||
|
||||
[profile.maxperf]
|
||||
inherits = "release"
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
codegen-units = 1
|
||||
|
||||
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
||||
.PHONY: fmt dev_docker build_test_docker run_test_docker clean update
|
||||
|
||||
L2GETH_TAG=scroll-v5.8.23
|
||||
L2GETH_TAG=scroll-v5.9.7
|
||||
|
||||
help: ## Display this help message
|
||||
@grep -h \
|
||||
|
||||
@@ -10,15 +10,15 @@ require (
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/pressly/goose/v3 v3.16.0
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626101020-47bc86cd961c
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
golang.org/x/sync v0.11.0
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
|
||||
)
|
||||
|
||||
replace github.com/scroll-tech/go-ethereum => github.com/scroll-tech/go-ethereum v1.10.14-0.20250626101020-47bc86cd961c // It's a hotfix for the header hash incompatibility issue, pls change this with caution
|
||||
replace github.com/scroll-tech/go-ethereum => github.com/scroll-tech/go-ethereum v1.10.14-0.20251017081611-2bc7a5482dcc // It's a hotfix for the header hash incompatibility issue, pls change this with caution
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
@@ -30,10 +30,10 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/consensys/bavard v0.1.13 // indirect
|
||||
github.com/consensys/gnark-crypto v0.13.0 // indirect
|
||||
github.com/consensys/bavard v0.1.27 // indirect
|
||||
github.com/consensys/gnark-crypto v0.16.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
@@ -41,7 +41,7 @@ require (
|
||||
github.com/docker/docker v26.1.0+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
|
||||
github.com/fjl/memsize v0.0.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
@@ -98,7 +98,7 @@ require (
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/status-im/keycard-go v0.2.0 // indirect
|
||||
github.com/supranational/blst v0.3.13 // indirect
|
||||
github.com/supranational/blst v0.3.15 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
@@ -110,7 +110,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.5.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
|
||||
@@ -53,16 +53,16 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
|
||||
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
||||
github.com/consensys/gnark-crypto v0.13.0 h1:VPULb/v6bbYELAPTDFINEVaMTTybV5GLxDdcjnS+4oc=
|
||||
github.com/consensys/gnark-crypto v0.13.0/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o=
|
||||
github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs=
|
||||
github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
|
||||
github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw4KoTAawo=
|
||||
github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
|
||||
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
||||
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -88,8 +88,8 @@ github.com/elastic/go-sysinfo v1.11.1 h1:g9mwl05njS4r69TisC+vwHWTSKywZFYYUu3so3T
|
||||
github.com/elastic/go-sysinfo v1.11.1/go.mod h1:6KQb31j0QeWBDF88jIdWSxE8cwoOB9tO4Y4osN7Q70E=
|
||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
||||
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -214,8 +214,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
|
||||
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@@ -309,10 +309,10 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6 h1:vb2XLvQwCf+F/ifP6P/lfeiQrHY6+Yb/E3R4KHXLqSE=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626101020-47bc86cd961c h1:IpEBKM6O+xOK2qZVZztGxcobFXkKMb5hAkBEVzfXjVg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626101020-47bc86cd961c/go.mod h1:pDCZ4iGvEGmdIe4aSAGBrb7XSrKEML6/L/wEMmNxOdk=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178 h1:4utngmJHXSOS5FoSdZhEV1xMRirpArbXvyoCZY9nYj0=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017081611-2bc7a5482dcc h1:zSO+VMyzmEVezVuMC7jZ9PcvihwmrlKt+7cyv9rpq2s=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017081611-2bc7a5482dcc/go.mod h1:zRa7CnS75mFdgp8IeMtZV/wCAlxPRT33Ek3+fFbBJVQ=
|
||||
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
|
||||
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
@@ -341,10 +341,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk=
|
||||
github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.15 h1:rd9viN6tfARE5wv3KZJ9H8e1cg0jXW8syFCcsbHa76o=
|
||||
github.com/supranational/blst v0.3.15/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
@@ -387,8 +387,8 @@ golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
||||
@@ -38,6 +38,7 @@ type FetcherConfig struct {
|
||||
BeaconNodeAPIEndpoint string `json:"BeaconNodeAPIEndpoint"`
|
||||
BlobScanAPIEndpoint string `json:"BlobScanAPIEndpoint"`
|
||||
BlockNativeAPIEndpoint string `json:"BlockNativeAPIEndpoint"`
|
||||
AwsS3Endpoint string `json:"AwsS3Endpoint"`
|
||||
}
|
||||
|
||||
// RedisConfig redis config
|
||||
|
||||
@@ -39,6 +39,9 @@ type L1MessageFetcher struct {
|
||||
// NewL1MessageFetcher creates a new L1MessageFetcher instance.
|
||||
func NewL1MessageFetcher(ctx context.Context, cfg *config.FetcherConfig, db *gorm.DB, client *ethclient.Client) (*L1MessageFetcher, error) {
|
||||
blobClient := blob_client.NewBlobClients()
|
||||
if cfg.AwsS3Endpoint != "" {
|
||||
blobClient.AddBlobClient(blob_client.NewAwsS3Client(cfg.AwsS3Endpoint))
|
||||
}
|
||||
if cfg.BeaconNodeAPIEndpoint != "" {
|
||||
beaconNodeClient, err := blob_client.NewBeaconNodeClient(cfg.BeaconNodeAPIEndpoint)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,3 +4,5 @@ docs/
|
||||
l2geth/
|
||||
rpc-gateway/
|
||||
*target/*
|
||||
|
||||
permissionless-batches/conf/
|
||||
@@ -4,3 +4,5 @@ docs/
|
||||
l2geth/
|
||||
rpc-gateway/
|
||||
*target/*
|
||||
|
||||
permissionless-batches/conf/
|
||||
@@ -4,3 +4,5 @@ docs/
|
||||
l2geth/
|
||||
rpc-gateway/
|
||||
*target/*
|
||||
|
||||
permissionless-batches/conf/
|
||||
@@ -1,5 +1,8 @@
|
||||
assets/
|
||||
contracts/
|
||||
docs/
|
||||
l2geth/
|
||||
rpc-gateway/
|
||||
*target/*
|
||||
*target/*
|
||||
|
||||
permissionless-batches/conf/
|
||||
30
build/dockerfiles/recovery_permissionless_batches.Dockerfile
Normal file
30
build/dockerfiles/recovery_permissionless_batches.Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
# Download Go dependencies
|
||||
FROM scrolltech/go-rust-builder:go-1.21-rust-nightly-2023-12-03 as base
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.work* ./
|
||||
COPY ./rollup/go.* ./rollup/
|
||||
COPY ./common/go.* ./common/
|
||||
COPY ./coordinator/go.* ./coordinator/
|
||||
COPY ./database/go.* ./database/
|
||||
COPY ./tests/integration-test/go.* ./tests/integration-test/
|
||||
COPY ./bridge-history-api/go.* ./bridge-history-api/
|
||||
RUN go mod download -x
|
||||
|
||||
# Build rollup_relayer
|
||||
FROM base as builder
|
||||
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
cd /src/rollup/cmd/permissionless_batches/ && CGO_LDFLAGS="-ldl" go build -v -p 4 -o /bin/rollup_relayer
|
||||
|
||||
# Pull rollup_relayer into a second stage deploy ubuntu container
|
||||
FROM ubuntu:20.04
|
||||
|
||||
RUN apt update && apt install vim netcat-openbsd net-tools curl ca-certificates -y
|
||||
|
||||
ENV CGO_LDFLAGS="-ldl"
|
||||
|
||||
COPY --from=builder /bin/rollup_relayer /bin/
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["rollup_relayer"]
|
||||
@@ -0,0 +1,8 @@
|
||||
assets/
|
||||
contracts/
|
||||
docs/
|
||||
l2geth/
|
||||
rpc-gateway/
|
||||
*target/*
|
||||
|
||||
permissionless-batches/conf/
|
||||
@@ -1,5 +1,8 @@
|
||||
assets/
|
||||
contracts/
|
||||
docs/
|
||||
l2geth/
|
||||
rpc-gateway/
|
||||
*target/*
|
||||
*target/*
|
||||
|
||||
permissionless-batches/conf/
|
||||
@@ -15,7 +15,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305151038-478940e79601
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/testcontainers/testcontainers-go v0.30.0
|
||||
github.com/testcontainers/testcontainers-go/modules/compose v0.30.0
|
||||
@@ -64,7 +64,7 @@ require (
|
||||
github.com/containerd/typeurl/v2 v2.1.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
@@ -79,7 +79,7 @@ require (
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fjl/memsize v0.0.2 // indirect
|
||||
github.com/fsnotify/fsevents v0.1.1 // indirect
|
||||
@@ -184,7 +184,7 @@ require (
|
||||
github.com/rjeczalik/notify v0.9.1 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250310095435-012aaee6b435 // indirect
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178 // indirect
|
||||
github.com/scroll-tech/zktrie v0.8.4 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
|
||||
@@ -198,7 +198,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.4.0 // indirect
|
||||
github.com/status-im/keycard-go v0.2.0 // indirect
|
||||
github.com/supranational/blst v0.3.13 // indirect
|
||||
github.com/supranational/blst v0.3.15 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect
|
||||
|
||||
@@ -155,8 +155,8 @@ github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoY
|
||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
@@ -214,8 +214,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||
@@ -636,10 +636,10 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250310095435-012aaee6b435 h1:X9fkvjrYBY79lGgKEPpUhuiJ4vWpWwzOVw4H8CU8L54=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250310095435-012aaee6b435/go.mod h1:yhTS9OVC0xQGhg7DN5iV5KZJvnSIlFWAxDdp+6jxQtY=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305151038-478940e79601 h1:NEsjCG6uSvLRBlsP3+x6PL1kM+Ojs3g8UGotIPgJSz8=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305151038-478940e79601/go.mod h1:OblWe1+QrZwdpwO0j/LY3BSGuKT3YPUFBDQQgvvfStQ=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178 h1:4utngmJHXSOS5FoSdZhEV1xMRirpArbXvyoCZY9nYj0=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63 h1:xuqdhD4w/zcI5T8Ty1wHvqB75P2HNg3jTH/kUEHGt9Y=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63/go.mod h1:zRa7CnS75mFdgp8IeMtZV/wCAlxPRT33Ek3+fFbBJVQ=
|
||||
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
|
||||
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
|
||||
@@ -707,8 +707,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk=
|
||||
github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/supranational/blst v0.3.15 h1:rd9viN6tfARE5wv3KZJ9H8e1cg0jXW8syFCcsbHa76o=
|
||||
github.com/supranational/blst v0.3.15/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E=
|
||||
|
||||
@@ -135,10 +135,18 @@ type BlockContextV2 struct {
|
||||
NumL1Msgs uint16 `json:"num_l1_msgs"`
|
||||
}
|
||||
|
||||
// Metric data carried with OpenVMProof
|
||||
type OpenVMProofStat struct {
|
||||
TotalCycle uint64 `json:"total_cycles"`
|
||||
ExecutionTimeMills uint64 `json:"execution_time_mills"`
|
||||
ProvingTimeMills uint64 `json:"proving_time_mills"`
|
||||
}
|
||||
|
||||
// Proof for flatten VM proof
|
||||
type OpenVMProof struct {
|
||||
Proof []byte `json:"proofs"`
|
||||
PublicValues []byte `json:"public_values"`
|
||||
Proof []byte `json:"proofs"`
|
||||
PublicValues []byte `json:"public_values"`
|
||||
Stat *OpenVMProofStat `json:"stat,omitempty"`
|
||||
}
|
||||
|
||||
// Proof for flatten EVM proof
|
||||
@@ -150,7 +158,8 @@ type OpenVMEvmProof struct {
|
||||
// OpenVMChunkProof includes the proof info that are required for chunk verification and rollup.
|
||||
type OpenVMChunkProof struct {
|
||||
MetaData struct {
|
||||
ChunkInfo *ChunkInfo `json:"chunk_info"`
|
||||
ChunkInfo *ChunkInfo `json:"chunk_info"`
|
||||
TotalGasUsed uint64 `json:"chunk_total_gas"`
|
||||
} `json:"metadata"`
|
||||
|
||||
VmProof *OpenVMProof `json:"proof"`
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var tag = "v4.5.29"
|
||||
var tag = "v4.5.49"
|
||||
|
||||
var commit = func() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: lint docker clean coordinator coordinator_skip_libzkp mock_coordinator
|
||||
.PHONY: lint docker clean coordinator coordinator_skip_libzkp mock_coordinator libzkp
|
||||
|
||||
IMAGE_VERSION=latest
|
||||
REPO_ROOT_DIR=./..
|
||||
@@ -34,6 +34,13 @@ coordinator_cron:
|
||||
coordinator_tool:
|
||||
go build -ldflags "-X scroll-tech/common/version.ZkVersion=${ZK_VERSION}" -o $(PWD)/build/bin/coordinator_tool ./cmd/tool
|
||||
|
||||
localsetup: coordinator_api ## Local setup: build coordinator_api, copy config, and setup releases
|
||||
@echo "Copying configuration files..."
|
||||
cp -r $(PWD)/conf $(PWD)/build/bin/
|
||||
@echo "Setting up releases..."
|
||||
cd $(PWD)/build && bash setup_releases.sh
|
||||
|
||||
|
||||
#coordinator_api_skip_libzkp:
|
||||
# go build -ldflags "-X scroll-tech/common/version.ZkVersion=${ZK_VERSION}" -o $(PWD)/build/bin/coordinator_api ./cmd/api
|
||||
|
||||
|
||||
62
coordinator/build/setup_releases.sh
Normal file
62
coordinator/build/setup_releases.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
# release version
|
||||
if [ -z "${SCROLL_ZKVM_VERSION}" ]; then
|
||||
echo "SCROLL_ZKVM_VERSION not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# set ASSET_DIR by reading from config.json
|
||||
CONFIG_FILE="bin/conf/config.json"
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Config file $CONFIG_FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# get the number of verifiers in the array
|
||||
VERIFIER_COUNT=$(jq -r '.prover_manager.verifier.verifiers | length' "$CONFIG_FILE")
|
||||
|
||||
if [ "$VERIFIER_COUNT" = "null" ] || [ "$VERIFIER_COUNT" -eq 0 ]; then
|
||||
echo "No verifiers found in config file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found $VERIFIER_COUNT verifier(s) in config"
|
||||
|
||||
# iterate through each verifier entry
|
||||
for ((i=0; i<$VERIFIER_COUNT; i++)); do
|
||||
# extract assets_path for current verifier
|
||||
ASSETS_PATH=$(jq -r ".prover_manager.verifier.verifiers[$i].assets_path" "$CONFIG_FILE")
|
||||
FORK_NAME=$(jq -r ".prover_manager.verifier.verifiers[$i].fork_name" "$CONFIG_FILE")
|
||||
|
||||
if [ "$ASSETS_PATH" = "null" ]; then
|
||||
echo "Warning: Could not find assets_path for verifier $i, skipping..."
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Processing verifier $i ($FORK_NAME): assets_path=$ASSETS_PATH"
|
||||
|
||||
# check if it's an absolute path (starts with /)
|
||||
if [[ "$ASSETS_PATH" = /* ]]; then
|
||||
# absolute path, use as is
|
||||
ASSET_DIR="$ASSETS_PATH"
|
||||
else
|
||||
# relative path, prefix with "bin/"
|
||||
ASSET_DIR="bin/$ASSETS_PATH"
|
||||
fi
|
||||
|
||||
echo "Using ASSET_DIR: $ASSET_DIR"
|
||||
|
||||
# create directory if it doesn't exist
|
||||
mkdir -p "$ASSET_DIR"
|
||||
|
||||
# assets for verifier-only mode
|
||||
echo "Downloading assets for $FORK_NAME to $ASSET_DIR..."
|
||||
wget https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/verifier/verifier.bin -O ${ASSET_DIR}/verifier.bin
|
||||
wget https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/verifier/openVmVk.json -O ${ASSET_DIR}/openVmVk.json
|
||||
|
||||
echo "Completed downloading assets for $FORK_NAME"
|
||||
echo "---"
|
||||
done
|
||||
|
||||
echo "All verifier assets downloaded successfully"
|
||||
@@ -12,7 +12,7 @@
|
||||
{
|
||||
"assets_path": "assets",
|
||||
"fork_name": "euclidV2"
|
||||
},
|
||||
},
|
||||
{
|
||||
"assets_path": "assets",
|
||||
"fork_name": "feynman"
|
||||
|
||||
@@ -9,8 +9,8 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
@@ -54,11 +54,11 @@ require (
|
||||
github.com/consensys/bavard v0.1.29 // indirect
|
||||
github.com/consensys/gnark-crypto v0.16.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
|
||||
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
|
||||
github.com/fjl/memsize v0.0.2 // indirect
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
@@ -92,7 +92,7 @@ require (
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 // indirect
|
||||
github.com/supranational/blst v0.3.13 // indirect
|
||||
github.com/supranational/blst v0.3.15 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
|
||||
@@ -47,8 +47,8 @@ github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw
|
||||
github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -59,8 +59,8 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vs
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
||||
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -253,10 +253,10 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6 h1:vb2XLvQwCf+F/ifP6P/lfeiQrHY6+Yb/E3R4KHXLqSE=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7 h1:1rN1qocsQlOyk1VCpIEF1J5pfQbLAi1pnMZSLQS37jQ=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7/go.mod h1:pDCZ4iGvEGmdIe4aSAGBrb7XSrKEML6/L/wEMmNxOdk=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178 h1:4utngmJHXSOS5FoSdZhEV1xMRirpArbXvyoCZY9nYj0=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63 h1:xuqdhD4w/zcI5T8Ty1wHvqB75P2HNg3jTH/kUEHGt9Y=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63/go.mod h1:zRa7CnS75mFdgp8IeMtZV/wCAlxPRT33Ek3+fFbBJVQ=
|
||||
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
|
||||
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
@@ -282,8 +282,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk=
|
||||
github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/supranational/blst v0.3.15 h1:rd9viN6tfARE5wv3KZJ9H8e1cg0jXW8syFCcsbHa76o=
|
||||
github.com/supranational/blst v0.3.15/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
|
||||
@@ -57,14 +57,16 @@ type Config struct {
|
||||
|
||||
// AssetConfig contain assets configurated for each fork, the defaul vkfile name is "OpenVmVk.json".
|
||||
type AssetConfig struct {
|
||||
AssetsPath string `json:"assets_path"`
|
||||
ForkName string `json:"fork_name"`
|
||||
Vkfile string `json:"vk_file,omitempty"`
|
||||
AssetsPath string `json:"assets_path"`
|
||||
ForkName string `json:"fork_name"`
|
||||
Vkfile string `json:"vk_file,omitempty"`
|
||||
MinProverVersion string `json:"min_prover_version,omitempty"`
|
||||
}
|
||||
|
||||
// VerifierConfig load zk verifier config.
|
||||
type VerifierConfig struct {
|
||||
MinProverVersion string `json:"min_prover_version"`
|
||||
Features string `json:"features,omitempty"`
|
||||
Verifiers []AssetConfig `json:"verifiers"`
|
||||
}
|
||||
|
||||
|
||||
@@ -24,18 +24,16 @@ type LoginLogic struct {
|
||||
|
||||
openVmVks map[string]struct{}
|
||||
|
||||
proverVersionHardForkMap map[string][]string
|
||||
proverVersionHardForkMap map[string]string
|
||||
}
|
||||
|
||||
// NewLoginLogic new a LoginLogic
|
||||
func NewLoginLogic(db *gorm.DB, cfg *config.Config, vf *verifier.Verifier) *LoginLogic {
|
||||
proverVersionHardForkMap := make(map[string][]string)
|
||||
proverVersionHardForkMap := make(map[string]string)
|
||||
|
||||
var hardForks []string
|
||||
for _, cfg := range cfg.ProverManager.Verifier.Verifiers {
|
||||
hardForks = append(hardForks, cfg.ForkName)
|
||||
proverVersionHardForkMap[cfg.ForkName] = cfg.MinProverVersion
|
||||
}
|
||||
proverVersionHardForkMap[cfg.ProverManager.Verifier.MinProverVersion] = hardForks
|
||||
|
||||
return &LoginLogic{
|
||||
cfg: cfg,
|
||||
@@ -101,9 +99,15 @@ func (l *LoginLogic) ProverHardForkName(login *types.LoginParameter) (string, er
|
||||
}
|
||||
|
||||
proverVersion := proverVersionSplits[0]
|
||||
if hardForkNames, ok := l.proverVersionHardForkMap[proverVersion]; ok {
|
||||
return strings.Join(hardForkNames, ","), nil
|
||||
var hardForkNames []string
|
||||
for n, minVersion := range l.proverVersionHardForkMap {
|
||||
if minVersion == "" || version.CheckScrollRepoVersion(proverVersion, minVersion) {
|
||||
hardForkNames = append(hardForkNames, n)
|
||||
}
|
||||
}
|
||||
if len(hardForkNames) == 0 {
|
||||
return "", fmt.Errorf("invalid prover prover_version:%s", login.Message.ProverVersion)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("invalid prover prover_version:%s", login.Message.ProverVersion)
|
||||
return strings.Join(hardForkNames, ","), nil
|
||||
}
|
||||
|
||||
@@ -140,3 +140,10 @@ func DumpVk(forkName, filePath string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set dynamic feature flags that control libzkp runtime behavior
|
||||
func SetDynamicFeature(feats string) {
|
||||
cFeats := goToCString(feats)
|
||||
defer freeCString(cFeats)
|
||||
C.set_dynamic_feature(cFeats)
|
||||
}
|
||||
|
||||
@@ -54,4 +54,7 @@ char* gen_wrapped_proof(char* proof_json, char* metadata, char* vk, size_t vk_le
|
||||
// Release memory allocated for a string returned by gen_wrapped_proof
|
||||
void release_string(char* string_ptr);
|
||||
|
||||
void set_dynamic_feature(const char* feats);
|
||||
|
||||
|
||||
#endif /* LIBZKP_H */
|
||||
|
||||
@@ -86,6 +86,10 @@ func (bp *BatchProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinato
|
||||
var tmpBatchTask *orm.Batch
|
||||
|
||||
if taskCtx.hasAssignedTask != nil {
|
||||
if taskCtx.hasAssignedTask.TaskType != int16(message.ProofTypeBatch) {
|
||||
return nil, fmt.Errorf("prover with publicKey %s is already assigned a task. ProverName: %s, ProverVersion: %s", taskCtx.PublicKey, taskCtx.ProverName, taskCtx.ProverVersion)
|
||||
}
|
||||
|
||||
tmpBatchTask, getTaskError = bp.batchOrm.GetBatchByHash(ctx.Copy(), taskCtx.hasAssignedTask.TaskID)
|
||||
if getTaskError != nil {
|
||||
log.Error("failed to get batch has assigned to prover", "taskID", taskCtx.hasAssignedTask.TaskID, "err", getTaskError)
|
||||
@@ -95,6 +99,14 @@ func (bp *BatchProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinato
|
||||
return nil, fmt.Errorf("prover with publicKey %s is already assigned a dropped batch. ProverName: %s, ProverVersion: %s",
|
||||
taskCtx.PublicKey, taskCtx.ProverName, taskCtx.ProverVersion)
|
||||
}
|
||||
} else if getTaskParameter.TaskID != "" {
|
||||
tmpBatchTask, getTaskError = bp.batchOrm.GetBatchByHash(ctx.Copy(), getTaskParameter.TaskID)
|
||||
if getTaskError != nil {
|
||||
log.Error("failed to get expected batch", "taskID", getTaskParameter.TaskID, "err", getTaskError)
|
||||
return nil, ErrCoordinatorInternalFailure
|
||||
} else if tmpBatchTask == nil {
|
||||
return nil, fmt.Errorf("Expected task (%s) is already dropped", getTaskParameter.TaskID)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpBatchTask == nil {
|
||||
|
||||
@@ -84,6 +84,10 @@ func (bp *BundleProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinat
|
||||
var tmpBundleTask *orm.Bundle
|
||||
|
||||
if taskCtx.hasAssignedTask != nil {
|
||||
if taskCtx.hasAssignedTask.TaskType != int16(message.ProofTypeBundle) {
|
||||
return nil, fmt.Errorf("prover with publicKey %s is already assigned a task. ProverName: %s, ProverVersion: %s", taskCtx.PublicKey, taskCtx.ProverName, taskCtx.ProverVersion)
|
||||
}
|
||||
|
||||
tmpBundleTask, getTaskError = bp.bundleOrm.GetBundleByHash(ctx.Copy(), taskCtx.hasAssignedTask.TaskID)
|
||||
if getTaskError != nil {
|
||||
log.Error("failed to get bundle has assigned to prover", "taskID", taskCtx.hasAssignedTask.TaskID, "err", getTaskError)
|
||||
@@ -93,6 +97,14 @@ func (bp *BundleProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinat
|
||||
return nil, fmt.Errorf("prover with publicKey %s is already assigned a dropped bundle. ProverName: %s, ProverVersion: %s",
|
||||
taskCtx.PublicKey, taskCtx.ProverName, taskCtx.ProverVersion)
|
||||
}
|
||||
} else if getTaskParameter.TaskID != "" {
|
||||
tmpBundleTask, getTaskError = bp.bundleOrm.GetBundleByHash(ctx.Copy(), getTaskParameter.TaskID)
|
||||
if getTaskError != nil {
|
||||
log.Error("failed to get expected bundle", "taskID", getTaskParameter.TaskID, "err", getTaskError)
|
||||
return nil, ErrCoordinatorInternalFailure
|
||||
} else if tmpBundleTask == nil {
|
||||
return nil, fmt.Errorf("Expected task (%s) is already dropped", getTaskParameter.TaskID)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpBundleTask == nil {
|
||||
@@ -234,9 +246,14 @@ func (bp *BundleProverTask) formatProverTask(ctx context.Context, task *orm.Prov
|
||||
return nil, fmt.Errorf("failed to get batch proofs for bundle task id:%s, no batch found", task.TaskID)
|
||||
}
|
||||
|
||||
parentBatch, err := bp.batchOrm.GetBatchByHash(ctx, batches[0].ParentBatchHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get parent batch for batch task id:%s err:%w", task.TaskID, err)
|
||||
var prevStateRoot common.Hash
|
||||
// this would be common in test cases: the first batch has empty parent
|
||||
if batches[0].Index > 1 {
|
||||
parentBatch, err := bp.batchOrm.GetBatchByHash(ctx, batches[0].ParentBatchHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get parent batch for batch task id:%s err:%w", task.TaskID, err)
|
||||
}
|
||||
prevStateRoot = common.HexToHash(parentBatch.StateRoot)
|
||||
}
|
||||
|
||||
var batchProofs []*message.OpenVMBatchProof
|
||||
@@ -255,7 +272,7 @@ func (bp *BundleProverTask) formatProverTask(ctx context.Context, task *orm.Prov
|
||||
|
||||
taskDetail.BundleInfo = &message.OpenVMBundleInfo{
|
||||
ChainID: bp.cfg.L2.ChainID,
|
||||
PrevStateRoot: common.HexToHash(parentBatch.StateRoot),
|
||||
PrevStateRoot: prevStateRoot,
|
||||
PostStateRoot: common.HexToHash(batches[len(batches)-1].StateRoot),
|
||||
WithdrawRoot: common.HexToHash(batches[len(batches)-1].WithdrawRoot),
|
||||
NumBatches: uint32(len(batches)),
|
||||
|
||||
@@ -80,7 +80,12 @@ func (cp *ChunkProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinato
|
||||
for i := 0; i < 5; i++ {
|
||||
var getTaskError error
|
||||
var tmpChunkTask *orm.Chunk
|
||||
|
||||
if taskCtx.hasAssignedTask != nil {
|
||||
if taskCtx.hasAssignedTask.TaskType != int16(message.ProofTypeChunk) {
|
||||
return nil, fmt.Errorf("prover with publicKey %s is already assigned a task. ProverName: %s, ProverVersion: %s", taskCtx.PublicKey, taskCtx.ProverName, taskCtx.ProverVersion)
|
||||
}
|
||||
|
||||
log.Debug("retrieved assigned task chunk", "taskID", taskCtx.hasAssignedTask.TaskID, "prover", taskCtx.ProverName)
|
||||
tmpChunkTask, getTaskError = cp.chunkOrm.GetChunkByHash(ctx.Copy(), taskCtx.hasAssignedTask.TaskID)
|
||||
if getTaskError != nil {
|
||||
@@ -91,6 +96,14 @@ func (cp *ChunkProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinato
|
||||
return nil, fmt.Errorf("prover with publicKey %s is already assigned a dropped chunk. ProverName: %s, ProverVersion: %s",
|
||||
taskCtx.PublicKey, taskCtx.ProverName, taskCtx.ProverVersion)
|
||||
}
|
||||
} else if getTaskParameter.TaskID != "" {
|
||||
tmpChunkTask, getTaskError = cp.chunkOrm.GetChunkByHash(ctx.Copy(), getTaskParameter.TaskID)
|
||||
if getTaskError != nil {
|
||||
log.Error("failed to get expected chunk", "taskID", getTaskParameter.TaskID, "err", getTaskError)
|
||||
return nil, ErrCoordinatorInternalFailure
|
||||
} else if tmpChunkTask == nil {
|
||||
return nil, fmt.Errorf("Expected task (%s) is already dropped", getTaskParameter.TaskID)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpChunkTask == nil {
|
||||
@@ -221,7 +234,7 @@ func (cp *ChunkProverTask) formatProverTask(ctx context.Context, task *orm.Prove
|
||||
// Get block hashes.
|
||||
blockHashes, dbErr := cp.blockOrm.GetL2BlockHashesByChunkHash(ctx, task.TaskID)
|
||||
if dbErr != nil || len(blockHashes) == 0 {
|
||||
return nil, fmt.Errorf("failed to fetch block hashes of a chunk, chunk hash:%s err:%w", task.TaskID, dbErr)
|
||||
return nil, fmt.Errorf("failed to fetch block hashes of a chunk, chunk hash:%s err:%v", task.TaskID, dbErr)
|
||||
}
|
||||
|
||||
var taskDetailBytes []byte
|
||||
|
||||
@@ -71,6 +71,9 @@ type ProofReceiverLogic struct {
|
||||
validateFailureProverTaskStatusNotOk prometheus.Counter
|
||||
validateFailureProverTaskTimeout prometheus.Counter
|
||||
validateFailureProverTaskHaveVerifier prometheus.Counter
|
||||
proverSpeed *prometheus.GaugeVec
|
||||
provingTime prometheus.Gauge
|
||||
evmCyclePerGas prometheus.Gauge
|
||||
|
||||
ChunkTask provertask.ProverTask
|
||||
BundleTask provertask.ProverTask
|
||||
@@ -79,6 +82,7 @@ type ProofReceiverLogic struct {
|
||||
|
||||
// NewSubmitProofReceiverLogic create a proof receiver logic
|
||||
func NewSubmitProofReceiverLogic(cfg *config.ProverManager, chainCfg *params.ChainConfig, db *gorm.DB, vf *verifier.Verifier, reg prometheus.Registerer) *ProofReceiverLogic {
|
||||
|
||||
return &ProofReceiverLogic{
|
||||
chunkOrm: orm.NewChunk(db),
|
||||
batchOrm: orm.NewBatch(db),
|
||||
@@ -133,6 +137,18 @@ func NewSubmitProofReceiverLogic(cfg *config.ProverManager, chainCfg *params.Cha
|
||||
Name: "coordinator_validate_failure_submit_have_been_verifier",
|
||||
Help: "Total number of submit proof validate failure proof have been verifier.",
|
||||
}),
|
||||
evmCyclePerGas: promauto.With(reg).NewGauge(prometheus.GaugeOpts{
|
||||
Name: "evm_circuit_cycle_per_gas",
|
||||
Help: "VM cycles cost for a gas unit cost in evm execution",
|
||||
}),
|
||||
provingTime: promauto.With(reg).NewGauge(prometheus.GaugeOpts{
|
||||
Name: "chunk_proving_time",
|
||||
Help: "Wall clock time for chunk proving in second",
|
||||
}),
|
||||
proverSpeed: promauto.With(reg).NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "prover_speed",
|
||||
Help: "Cycle against running time of prover (in mhz)",
|
||||
}, []string{"type", "phase"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,12 +220,34 @@ func (m *ProofReceiverLogic) HandleZkProof(ctx *gin.Context, proofParameter coor
|
||||
return unmarshalErr
|
||||
}
|
||||
success, verifyErr = m.verifier.VerifyChunkProof(chunkProof, hardForkName)
|
||||
if stat := chunkProof.VmProof.Stat; stat != nil {
|
||||
if g, _ := m.proverSpeed.GetMetricWithLabelValues("chunk", "exec"); g != nil && stat.ExecutionTimeMills > 0 {
|
||||
g.Set(float64(stat.TotalCycle) / float64(stat.ExecutionTimeMills*1000))
|
||||
}
|
||||
if g, _ := m.proverSpeed.GetMetricWithLabelValues("chunk", "proving"); g != nil && stat.ProvingTimeMills > 0 {
|
||||
g.Set(float64(stat.TotalCycle) / float64(stat.ProvingTimeMills*1000))
|
||||
}
|
||||
if chunkProof.MetaData.TotalGasUsed > 0 {
|
||||
cycle_per_gas := float64(stat.TotalCycle) / float64(chunkProof.MetaData.TotalGasUsed)
|
||||
m.evmCyclePerGas.Set(cycle_per_gas)
|
||||
}
|
||||
m.provingTime.Set(float64(stat.ProvingTimeMills) / 1000)
|
||||
}
|
||||
|
||||
case message.ProofTypeBatch:
|
||||
batchProof := &message.OpenVMBatchProof{}
|
||||
if unmarshalErr := json.Unmarshal([]byte(proofParameter.Proof), &batchProof); unmarshalErr != nil {
|
||||
return unmarshalErr
|
||||
}
|
||||
success, verifyErr = m.verifier.VerifyBatchProof(batchProof, hardForkName)
|
||||
if stat := batchProof.VmProof.Stat; stat != nil {
|
||||
if g, _ := m.proverSpeed.GetMetricWithLabelValues("batch", "exec"); g != nil && stat.ExecutionTimeMills > 0 {
|
||||
g.Set(float64(stat.TotalCycle) / float64(stat.ExecutionTimeMills*1000))
|
||||
}
|
||||
if g, _ := m.proverSpeed.GetMetricWithLabelValues("batch", "proving"); g != nil && stat.ProvingTimeMills > 0 {
|
||||
g.Set(float64(stat.TotalCycle) / float64(stat.ProvingTimeMills*1000))
|
||||
}
|
||||
}
|
||||
case message.ProofTypeBundle:
|
||||
bundleProof := &message.OpenVMBundleProof{}
|
||||
if unmarshalErr := json.Unmarshal([]byte(proofParameter.Proof), &bundleProof); unmarshalErr != nil {
|
||||
|
||||
@@ -4,11 +4,14 @@ package verifier
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
|
||||
@@ -64,6 +67,9 @@ func NewVerifier(cfg *config.VerifierConfig) (*Verifier, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.Features != "" {
|
||||
libzkp.SetDynamicFeature(cfg.Features)
|
||||
}
|
||||
libzkp.InitVerifier(string(configBytes))
|
||||
|
||||
v := &Verifier{
|
||||
@@ -117,6 +123,33 @@ func (v *Verifier) VerifyBundleProof(proof *message.OpenVMBundleProof, forkName
|
||||
return libzkp.VerifyBundleProof(string(buf), forkName), nil
|
||||
}
|
||||
|
||||
/*
|
||||
add vk of imcompatilbe circuit app here to avoid we had used them unexpectedly
|
||||
25/07/15: 0.5.0rc0 is no longer compatible since a breaking change
|
||||
*/
|
||||
const blocked_vks = `
|
||||
rSJNNBpsxBdKlstbIIU/aYc7bHau98Qb2yjZMc5PmDhmGOolp5kYRbvF/VcWcO5HN5ujGs6S00W8pZcCoNQRLQ==,
|
||||
2Lo7Cebm6SFtcsYXipkcMxIBmVY7UpoMXik/Msm7t2nyvi9EaNGsSnDnaCurscYEF+IcdjPUtVtY9EcD7IKwWg==,
|
||||
D6YFHwTLZF/U2zpYJPQ3LwJZRm85yA5Vq2iFBqd3Mk4iwOUpS8sbOp3vg2+NDxhhKphgYpuUlykpdsoRhEt+cw==,
|
||||
`
|
||||
|
||||
// tries to decode s as hex, and if that fails, as base64.
|
||||
func decodeVkString(s string) ([]byte, error) {
|
||||
// Try hex decoding first
|
||||
if b, err := hex.DecodeString(s); err == nil {
|
||||
return b, nil
|
||||
}
|
||||
// Fallback to base64 decoding
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil, fmt.Errorf("decode vk string %s fail (empty bytes)", s)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (v *Verifier) loadOpenVMVks(cfg config.AssetConfig) error {
|
||||
|
||||
vkFileName := cfg.Vkfile
|
||||
@@ -138,22 +171,32 @@ func (v *Verifier) loadOpenVMVks(cfg config.AssetConfig) error {
|
||||
if err := json.Unmarshal(byt, &dump); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(blocked_vks, dump.Chunk) {
|
||||
return fmt.Errorf("loaded blocked chunk vk %s", dump.Chunk)
|
||||
}
|
||||
if strings.Contains(blocked_vks, dump.Batch) {
|
||||
return fmt.Errorf("loaded blocked batch vk %s", dump.Batch)
|
||||
}
|
||||
if strings.Contains(blocked_vks, dump.Bundle) {
|
||||
return fmt.Errorf("loaded blocked bundle vk %s", dump.Bundle)
|
||||
}
|
||||
|
||||
v.OpenVMVkMap[dump.Chunk] = struct{}{}
|
||||
v.OpenVMVkMap[dump.Batch] = struct{}{}
|
||||
v.OpenVMVkMap[dump.Bundle] = struct{}{}
|
||||
log.Info("Load vks", "from", cfg.AssetsPath, "chunk", dump.Chunk, "batch", dump.Batch, "bundle", dump.Bundle)
|
||||
|
||||
decodedBytes, err := base64.StdEncoding.DecodeString(dump.Chunk)
|
||||
decodedBytes, err := decodeVkString(dump.Chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.ChunkVk[cfg.ForkName] = decodedBytes
|
||||
decodedBytes, err = base64.StdEncoding.DecodeString(dump.Batch)
|
||||
decodedBytes, err = decodeVkString(dump.Batch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.BatchVk[cfg.ForkName] = decodedBytes
|
||||
decodedBytes, err = base64.StdEncoding.DecodeString(dump.Bundle)
|
||||
decodedBytes, err = decodeVkString(dump.Bundle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -584,7 +584,8 @@ func testTimeoutProof(t *testing.T) {
|
||||
err = chunkOrm.UpdateBatchHashInRange(context.Background(), 0, 100, batch.Hash)
|
||||
assert.NoError(t, err)
|
||||
encodeData, err := json.Marshal(message.OpenVMChunkProof{VmProof: &message.OpenVMProof{}, MetaData: struct {
|
||||
ChunkInfo *message.ChunkInfo `json:"chunk_info"`
|
||||
ChunkInfo *message.ChunkInfo `json:"chunk_info"`
|
||||
TotalGasUsed uint64 `json:"chunk_total_gas"`
|
||||
}{ChunkInfo: &message.ChunkInfo{}}})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, encodeData)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
[patch."https://github.com/openvm-org/openvm.git"]
|
||||
openvm-build = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-circuit = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-continuations = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-instructions ={ git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-native-circuit = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-native-compiler = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-native-recursion = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-native-transpiler = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-rv32im-transpiler = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
openvm-sdk = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false, features = ["parallel", "bench-metrics", "evm-prove"] }
|
||||
openvm-transpiler = { git = "ssh://git@github.com/scroll-tech/openvm-gpu.git", branch = "patch-v1.2.1-rc.1-pipe", default-features = false }
|
||||
|
||||
[patch."https://github.com/openvm-org/stark-backend.git"]
|
||||
openvm-stark-backend = { git = "ssh://git@github.com/scroll-tech/openvm-stark-gpu.git", branch = "sync/upstream-250702", features = ["gpu"] }
|
||||
openvm-stark-sdk = { git = "ssh://git@github.com/scroll-tech/openvm-stark-gpu.git", branch = "sync/upstream-250702", features = ["gpu"] }
|
||||
|
||||
[patch."https://github.com/Plonky3/Plonky3.git"]
|
||||
p3-air = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-field = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-commit = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-matrix = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-baby-bear = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", features = [
|
||||
"nightly-features",
|
||||
], rev = "450ec18" }
|
||||
p3-koala-bear = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-util = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-challenger = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-dft = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-fri = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-goldilocks = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-keccak = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-keccak-air = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-blake3 = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-mds = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-merkle-tree = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-monty-31 = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-poseidon = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-poseidon2 = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-poseidon2-air = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-symmetric = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-uni-stark = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
p3-maybe-rayon = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" } # the "parallel" feature is NOT on by default to allow single-threaded benchmarking
|
||||
p3-bn254-fr = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", rev = "450ec18" }
|
||||
@@ -13,6 +13,7 @@ libzkp = { path = "../libzkp" }
|
||||
alloy = { workspace = true, features = ["provider-http", "transport-http", "reqwest", "reqwest-rustls-tls", "json-rpc"] }
|
||||
sbv-primitives = { workspace = true, features = ["scroll"] }
|
||||
sbv-utils = { workspace = true, features = ["scroll"] }
|
||||
sbv-core = { workspace = true, features = ["scroll"] }
|
||||
|
||||
eyre.workspace = true
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ pub fn init(config: &str) -> eyre::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_client() -> rpc_client::RpcClient<'static> {
|
||||
pub fn get_client() -> impl libzkp::tasks::ChunkInterpreter {
|
||||
GLOBAL_L2GETH_CLI
|
||||
.get()
|
||||
.expect("must has been inited")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use alloy::{
|
||||
providers::{Provider, ProviderBuilder, RootProvider},
|
||||
providers::{Provider, ProviderBuilder},
|
||||
rpc::client::ClientBuilder,
|
||||
transports::layers::RetryBackoffLayer,
|
||||
};
|
||||
@@ -49,13 +49,13 @@ pub struct RpcConfig {
|
||||
/// so it can be run in block mode (i.e. inside dynamic library without a global entry)
|
||||
pub struct RpcClientCore {
|
||||
/// rpc prover
|
||||
provider: RootProvider<Network>,
|
||||
client: alloy::rpc::client::RpcClient,
|
||||
rt: tokio::runtime::Runtime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RpcClient<'a> {
|
||||
provider: &'a RootProvider<Network>,
|
||||
pub struct RpcClient<'a, T: Provider<Network>> {
|
||||
provider: T,
|
||||
handle: &'a tokio::runtime::Handle,
|
||||
}
|
||||
|
||||
@@ -75,76 +75,78 @@ impl RpcClientCore {
|
||||
let retry_layer = RetryBackoffLayer::new(config.max_retry, config.backoff, config.cups);
|
||||
let client = ClientBuilder::default().layer(retry_layer).http(rpc);
|
||||
|
||||
Ok(Self {
|
||||
provider: ProviderBuilder::<_, _, Network>::default().connect_client(client),
|
||||
rt,
|
||||
})
|
||||
Ok(Self { client, rt })
|
||||
}
|
||||
|
||||
pub fn get_client(&self) -> RpcClient {
|
||||
pub fn get_client(&self) -> RpcClient<'_, impl Provider<Network>> {
|
||||
RpcClient {
|
||||
provider: &self.provider,
|
||||
provider: ProviderBuilder::<_, _, Network>::default()
|
||||
.connect_client(self.client.clone()),
|
||||
handle: self.rt.handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChunkInterpreter for RpcClient<'_> {
|
||||
impl<T: Provider<Network>> ChunkInterpreter for RpcClient<'_, T> {
|
||||
fn try_fetch_block_witness(
|
||||
&self,
|
||||
block_hash: sbv_primitives::B256,
|
||||
prev_witness: Option<&sbv_primitives::types::BlockWitness>,
|
||||
) -> Result<sbv_primitives::types::BlockWitness> {
|
||||
prev_witness: Option<&sbv_core::BlockWitness>,
|
||||
) -> Result<sbv_core::BlockWitness> {
|
||||
async fn fetch_witness_async(
|
||||
provider: &RootProvider<Network>,
|
||||
provider: impl Provider<Network>,
|
||||
block_hash: sbv_primitives::B256,
|
||||
prev_witness: Option<&sbv_primitives::types::BlockWitness>,
|
||||
) -> Result<sbv_primitives::types::BlockWitness> {
|
||||
use sbv_utils::{rpc::ProviderExt, witness::WitnessBuilder};
|
||||
prev_witness: Option<&sbv_core::BlockWitness>,
|
||||
) -> Result<sbv_core::BlockWitness> {
|
||||
use sbv_utils::rpc::ProviderExt;
|
||||
|
||||
let chain_id = provider.get_chain_id().await?;
|
||||
|
||||
let block = provider
|
||||
.get_block_by_hash(block_hash)
|
||||
.full()
|
||||
.await?
|
||||
.ok_or_else(|| eyre::eyre!("Block not found"))?;
|
||||
|
||||
let number = block.header.number;
|
||||
if number == 0 {
|
||||
eyre::bail!("no number in header or use block 0");
|
||||
}
|
||||
|
||||
let prev_state_root = if let Some(witness) = prev_witness {
|
||||
if witness.header.number != number - 1 {
|
||||
eyre::bail!(
|
||||
"the ref witness is not the previous block, expected {} get {}",
|
||||
number - 1,
|
||||
witness.header.number,
|
||||
);
|
||||
}
|
||||
witness.header.state_root
|
||||
let (chain_id, block_num, prev_state_root) = if let Some(w) = prev_witness {
|
||||
(w.chain_id, w.header.number + 1, w.header.state_root)
|
||||
} else {
|
||||
provider
|
||||
.scroll_disk_root((number - 1).into())
|
||||
let chain_id = provider.get_chain_id().await?;
|
||||
let block = provider
|
||||
.get_block_by_hash(block_hash)
|
||||
.full()
|
||||
.await?
|
||||
.disk_root
|
||||
.ok_or_else(|| eyre::eyre!("Block {block_hash} not found"))?;
|
||||
|
||||
let parent_block = provider
|
||||
.get_block_by_hash(block.header.parent_hash)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
eyre::eyre!(
|
||||
"parent block for block {} should exist",
|
||||
block.header.number
|
||||
)
|
||||
})?;
|
||||
|
||||
(
|
||||
chain_id,
|
||||
block.header.number,
|
||||
parent_block.header.state_root,
|
||||
)
|
||||
};
|
||||
|
||||
let witness = WitnessBuilder::new()
|
||||
.block(block)
|
||||
.chain_id(chain_id)
|
||||
.execution_witness(provider.debug_execution_witness(number.into()).await?)
|
||||
.state_root(provider.scroll_disk_root(number.into()).await?.disk_root)?
|
||||
.prev_state_root(prev_state_root)
|
||||
.build()?;
|
||||
let req = provider
|
||||
.dump_block_witness(block_num)
|
||||
.with_chain_id(chain_id)
|
||||
.with_prev_state_root(prev_state_root);
|
||||
|
||||
let witness = req
|
||||
.send()
|
||||
.await
|
||||
.transpose()
|
||||
.ok_or_else(|| eyre::eyre!("Block witness {block_num} not available"))??;
|
||||
|
||||
Ok(witness)
|
||||
}
|
||||
|
||||
tracing::debug!("fetch witness for {block_hash}");
|
||||
self.handle
|
||||
.block_on(fetch_witness_async(self.provider, block_hash, prev_witness))
|
||||
self.handle.block_on(fetch_witness_async(
|
||||
&self.provider,
|
||||
block_hash,
|
||||
prev_witness,
|
||||
))
|
||||
}
|
||||
|
||||
fn try_fetch_storage_node(
|
||||
@@ -152,7 +154,7 @@ impl ChunkInterpreter for RpcClient<'_> {
|
||||
node_hash: sbv_primitives::B256,
|
||||
) -> Result<sbv_primitives::Bytes> {
|
||||
async fn fetch_storage_node_async(
|
||||
provider: &RootProvider<Network>,
|
||||
provider: impl Provider<Network>,
|
||||
node_hash: sbv_primitives::B256,
|
||||
) -> Result<sbv_primitives::Bytes> {
|
||||
let ret = provider
|
||||
@@ -164,7 +166,7 @@ impl ChunkInterpreter for RpcClient<'_> {
|
||||
|
||||
tracing::debug!("fetch storage node for {node_hash}");
|
||||
self.handle
|
||||
.block_on(fetch_storage_node_async(self.provider, node_hash))
|
||||
.block_on(fetch_storage_node_async(&self.provider, node_hash))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,10 +192,10 @@ mod tests {
|
||||
let client_core = RpcClientCore::create(&config).expect("Failed to create RPC client");
|
||||
let client = client_core.get_client();
|
||||
|
||||
// latest - 1 block in 2025.6.15
|
||||
// latest - 1 block in 2025.9.11
|
||||
let block_hash = B256::from(
|
||||
hex::const_decode_to_array(
|
||||
b"0x9535a6970bc4db9031749331a214e35ed8c8a3f585f6f456d590a0bc780a1368",
|
||||
b"0x093fb6bf2e556a659b35428ac447cd9f0635382fc40ffad417b5910824f9e932",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -203,10 +205,10 @@ mod tests {
|
||||
.try_fetch_block_witness(block_hash, None)
|
||||
.expect("should success");
|
||||
|
||||
// latest block in 2025.6.15
|
||||
// block selected in 2025.9.11
|
||||
let block_hash = B256::from(
|
||||
hex::const_decode_to_array(
|
||||
b"0xd47088cdb6afc68aa082e633bb7da9340d29c73841668afacfb9c1e66e557af0",
|
||||
b"0x77cc84dd7a4dedf6fe5fb9b443aeb5a4fb0623ad088a365d3232b7b23fc848e5",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -216,26 +218,4 @@ mod tests {
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&wit2).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Requires L2GETH_ENDPOINT environment variable"]
|
||||
fn test_try_fetch_storage_node() {
|
||||
let config = create_config_from_env();
|
||||
let client_core = RpcClientCore::create(&config).expect("Failed to create RPC client");
|
||||
let client = client_core.get_client();
|
||||
|
||||
// the root node (state root) of the block in unittest above
|
||||
let node_hash = B256::from(
|
||||
hex::const_decode_to_array(
|
||||
b"0xb9e67403a2eb35afbb0475fe942918cf9a330a1d7532704c24554506be62b27c",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// This is expected to fail since we're using a dummy hash, but it tests the code path
|
||||
let node = client
|
||||
.try_fetch_storage_node(node_hash)
|
||||
.expect("should success");
|
||||
println!("{}", serde_json::to_string_pretty(&node).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ edition.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
scroll-zkvm-types.workspace = true
|
||||
scroll-zkvm-verifier-euclid.workspace = true
|
||||
scroll-zkvm-verifier.workspace = true
|
||||
|
||||
alloy-primitives.workspace = true #depress the effect of "native-keccak"
|
||||
sbv-primitives.workspace = true
|
||||
sbv-primitives = {workspace = true, features = ["scroll-compress-ratio", "scroll"]}
|
||||
sbv-core = { workspace = true, features = ["scroll"] }
|
||||
base64.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
@@ -18,6 +19,7 @@ tracing.workspace = true
|
||||
eyre.workspace = true
|
||||
|
||||
git-version = "0.3.5"
|
||||
bincode = { version = "2", features = ["serde"] }
|
||||
serde_stacker = "0.1"
|
||||
regex = "1.11"
|
||||
c-kzg = { version = "2.0", features = ["serde"] }
|
||||
|
||||
@@ -5,12 +5,33 @@ pub use verifier::{TaskType, VerifierConfig};
|
||||
mod utils;
|
||||
|
||||
use sbv_primitives::B256;
|
||||
use scroll_zkvm_types::util::vec_as_base64;
|
||||
use scroll_zkvm_types::utils::vec_as_base64;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use std::path::Path;
|
||||
use tasks::chunk_interpreter::{ChunkInterpreter, TryFromWithInterpreter};
|
||||
|
||||
/// global features: use legacy encoding for witness
|
||||
static mut LEGACY_WITNESS_ENCODING: bool = false;
|
||||
pub(crate) fn witness_use_legacy_mode() -> bool {
|
||||
unsafe { LEGACY_WITNESS_ENCODING }
|
||||
}
|
||||
|
||||
pub fn set_dynamic_feature(feats: &str) {
|
||||
for feat_s in feats.split(':') {
|
||||
match feat_s.trim().to_lowercase().as_str() {
|
||||
"legacy_witness" => {
|
||||
tracing::info!("set witness encoding for legacy mode");
|
||||
unsafe {
|
||||
// the function is only called while initialize step
|
||||
LEGACY_WITNESS_ENCODING = true;
|
||||
}
|
||||
}
|
||||
s => tracing::warn!("unrecognized dynamic feature: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn the coordinator's chunk task into a json string for formal chunk proving
|
||||
/// task (with full witnesses)
|
||||
pub fn checkout_chunk_task(
|
||||
@@ -30,9 +51,8 @@ pub fn checkout_chunk_task(
|
||||
pub fn gen_universal_task(
|
||||
task_type: i32,
|
||||
task_json: &str,
|
||||
fork_name: &str,
|
||||
fork_name_str: &str,
|
||||
expected_vk: &[u8],
|
||||
interpreter: Option<impl ChunkInterpreter>,
|
||||
) -> eyre::Result<(B256, String, String)> {
|
||||
use proofs::*;
|
||||
use tasks::*;
|
||||
@@ -48,19 +68,39 @@ pub fn gen_universal_task(
|
||||
|
||||
let (pi_hash, metadata, mut u_task) = match task_type {
|
||||
x if x == TaskType::Chunk as i32 => {
|
||||
let task = serde_json::from_str::<ChunkProvingTask>(task_json)?;
|
||||
let mut task = serde_json::from_str::<ChunkProvingTask>(task_json)?;
|
||||
// normailze fork name field in task
|
||||
task.fork_name = task.fork_name.to_lowercase();
|
||||
// always respect the fork_name_str (which has been normalized) being passed
|
||||
// if the fork_name wrapped in task is not match, consider it a malformed task
|
||||
if fork_name_str != task.fork_name.as_str() {
|
||||
eyre::bail!("fork name in chunk task not match the calling arg, expected {fork_name_str}, get {}", task.fork_name);
|
||||
}
|
||||
let (pi_hash, metadata, u_task) =
|
||||
gen_universal_chunk_task(task, fork_name.into(), interpreter)?;
|
||||
utils::panic_catch(move || gen_universal_chunk_task(task, fork_name_str.into()))
|
||||
.map_err(|e| eyre::eyre!("caught panic in chunk task{e}"))??;
|
||||
(pi_hash, AnyMetaData::Chunk(metadata), u_task)
|
||||
}
|
||||
x if x == TaskType::Batch as i32 => {
|
||||
let task = serde_json::from_str::<BatchProvingTask>(task_json)?;
|
||||
let (pi_hash, metadata, u_task) = gen_universal_batch_task(task, fork_name.into())?;
|
||||
let mut task = serde_json::from_str::<BatchProvingTask>(task_json)?;
|
||||
task.fork_name = task.fork_name.to_lowercase();
|
||||
if fork_name_str != task.fork_name.as_str() {
|
||||
eyre::bail!("fork name in batch task not match the calling arg, expected {fork_name_str}, get {}", task.fork_name);
|
||||
}
|
||||
let (pi_hash, metadata, u_task) =
|
||||
utils::panic_catch(move || gen_universal_batch_task(task, fork_name_str.into()))
|
||||
.map_err(|e| eyre::eyre!("caught panic in chunk task{e}"))??;
|
||||
(pi_hash, AnyMetaData::Batch(metadata), u_task)
|
||||
}
|
||||
x if x == TaskType::Bundle as i32 => {
|
||||
let task = serde_json::from_str::<BundleProvingTask>(task_json)?;
|
||||
let (pi_hash, metadata, u_task) = gen_universal_bundle_task(task, fork_name.into())?;
|
||||
let mut task = serde_json::from_str::<BundleProvingTask>(task_json)?;
|
||||
task.fork_name = task.fork_name.to_lowercase();
|
||||
if fork_name_str != task.fork_name.as_str() {
|
||||
eyre::bail!("fork name in bundle task not match the calling arg, expected {fork_name_str}, get {}", task.fork_name);
|
||||
}
|
||||
let (pi_hash, metadata, u_task) =
|
||||
utils::panic_catch(move || gen_universal_bundle_task(task, fork_name_str.into()))
|
||||
.map_err(|e| eyre::eyre!("caught panic in chunk task{e}"))??;
|
||||
(pi_hash, AnyMetaData::Bundle(metadata), u_task)
|
||||
}
|
||||
_ => return Err(eyre::eyre!("unrecognized task type {task_type}")),
|
||||
@@ -111,24 +151,6 @@ pub fn verify_proof(proof: Vec<u8>, fork_name: &str, task_type: TaskType) -> eyr
|
||||
let verifier = verifier::get_verifier(fork_name)?;
|
||||
|
||||
let ret = verifier.lock().unwrap().verify(task_type, &proof)?;
|
||||
|
||||
if let Ok(debug_value) = std::env::var("ZKVM_DEBUG_PROOF") {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
if !ret && debug_value.to_lowercase() == "true" {
|
||||
// Dump req.input to a temporary file
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let filename = format!("/tmp/proof_{}.json", timestamp);
|
||||
if let Err(e) = std::fs::write(&filename, &proof) {
|
||||
eprintln!("Failed to write proof to file {}: {}", filename, e);
|
||||
} else {
|
||||
println!("Dumped failed proof to {}", filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ use scroll_zkvm_types::{
|
||||
batch::BatchInfo,
|
||||
bundle::BundleInfo,
|
||||
chunk::ChunkInfo,
|
||||
proof::{EvmProof, OpenVmEvmProof, ProofEnum, RootProof},
|
||||
proof::{EvmProof, OpenVmEvmProof, ProofEnum, StarkProof},
|
||||
public_inputs::{ForkName, MultiVersionPublicInputs},
|
||||
types_agg::{AggregationInput, ProgramCommitment},
|
||||
util::vec_as_base64,
|
||||
types_agg::AggregationInput,
|
||||
utils::{serialize_vk, vec_as_base64},
|
||||
};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
@@ -40,7 +40,7 @@ pub struct WrappedProof<Metadata> {
|
||||
}
|
||||
|
||||
pub trait AsRootProof {
|
||||
fn as_root_proof(&self) -> &RootProof;
|
||||
fn as_root_proof(&self) -> &StarkProof;
|
||||
}
|
||||
|
||||
pub trait AsEvmProof {
|
||||
@@ -61,17 +61,17 @@ pub type BatchProof = WrappedProof<BatchProofMetadata>;
|
||||
pub type BundleProof = WrappedProof<BundleProofMetadata>;
|
||||
|
||||
impl AsRootProof for ChunkProof {
|
||||
fn as_root_proof(&self) -> &RootProof {
|
||||
fn as_root_proof(&self) -> &StarkProof {
|
||||
self.proof
|
||||
.as_root_proof()
|
||||
.as_stark_proof()
|
||||
.expect("batch proof use root proof")
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRootProof for BatchProof {
|
||||
fn as_root_proof(&self) -> &RootProof {
|
||||
fn as_root_proof(&self) -> &StarkProof {
|
||||
self.proof
|
||||
.as_root_proof()
|
||||
.as_stark_proof()
|
||||
.expect("batch proof use root proof")
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,8 @@ pub trait PersistableProof: Sized {
|
||||
pub struct ChunkProofMetadata {
|
||||
/// The chunk information describing the list of blocks contained within the chunk.
|
||||
pub chunk_info: ChunkInfo,
|
||||
/// Additional data for stat
|
||||
pub chunk_total_gas: u64,
|
||||
}
|
||||
|
||||
impl ProofMetadata for ChunkProofMetadata {
|
||||
@@ -170,7 +172,7 @@ impl<Metadata> From<&WrappedProof<Metadata>> for AggregationInput {
|
||||
fn from(value: &WrappedProof<Metadata>) -> Self {
|
||||
Self {
|
||||
public_values: value.proof.public_values(),
|
||||
commitment: ProgramCommitment::deserialize(&value.vk),
|
||||
commitment: serialize_vk::deserialize(&value.vk),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,18 @@ pub use chunk::{ChunkProvingTask, ChunkTask};
|
||||
pub use chunk_interpreter::ChunkInterpreter;
|
||||
pub use scroll_zkvm_types::task::ProvingTask;
|
||||
|
||||
use crate::proofs::{self, BatchProofMetadata, BundleProofMetadata, ChunkProofMetadata};
|
||||
use crate::{
|
||||
proofs::{self, BatchProofMetadata, BundleProofMetadata, ChunkProofMetadata},
|
||||
utils::panic_catch,
|
||||
};
|
||||
use sbv_primitives::B256;
|
||||
use scroll_zkvm_types::public_inputs::{ForkName, MultiVersionPublicInputs};
|
||||
|
||||
fn encode_task_to_witness<T: serde::Serialize>(task: &T) -> eyre::Result<Vec<u8>> {
|
||||
let config = bincode::config::standard();
|
||||
Ok(bincode::serde::encode_to_vec(task, config)?)
|
||||
}
|
||||
|
||||
fn check_aggregation_proofs<Metadata>(
|
||||
proofs: &[proofs::WrappedProof<Metadata>],
|
||||
fork_name: ForkName,
|
||||
@@ -20,44 +28,33 @@ fn check_aggregation_proofs<Metadata>(
|
||||
where
|
||||
Metadata: proofs::ProofMetadata,
|
||||
{
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
|
||||
panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
panic_catch(|| {
|
||||
for w in proofs.windows(2) {
|
||||
w[1].metadata
|
||||
.pi_hash_info()
|
||||
.validate(w[0].metadata.pi_hash_info(), fork_name);
|
||||
}
|
||||
}))
|
||||
.map_err(|e| {
|
||||
let error_msg = if let Some(string) = e.downcast_ref::<String>() {
|
||||
string.clone()
|
||||
} else if let Some(str) = e.downcast_ref::<&str>() {
|
||||
str.to_string()
|
||||
} else {
|
||||
"Unknown validation error occurred".to_string()
|
||||
};
|
||||
eyre::eyre!("Chunk data validation failed: {}", error_msg)
|
||||
})?;
|
||||
})
|
||||
.map_err(|e| eyre::eyre!("Chunk data validation failed: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate required staff for chunk proving
|
||||
pub fn gen_universal_chunk_task(
|
||||
mut task: ChunkProvingTask,
|
||||
task: ChunkProvingTask,
|
||||
fork_name: ForkName,
|
||||
interpreter: Option<impl ChunkInterpreter>,
|
||||
) -> eyre::Result<(B256, ChunkProofMetadata, ProvingTask)> {
|
||||
if let Some(interpreter) = interpreter {
|
||||
task.prepare_task_via_interpret(interpreter)?;
|
||||
}
|
||||
let chunk_total_gas = task.stats().total_gas_used;
|
||||
let chunk_info = task.precheck_and_build_metadata()?;
|
||||
let proving_task = task.try_into()?;
|
||||
let expected_pi_hash = chunk_info.pi_hash_by_fork(fork_name);
|
||||
Ok((
|
||||
expected_pi_hash,
|
||||
ChunkProofMetadata { chunk_info },
|
||||
ChunkProofMetadata {
|
||||
chunk_info,
|
||||
chunk_total_gas,
|
||||
},
|
||||
proving_task,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ use eyre::Result;
|
||||
use sbv_primitives::{B256, U256};
|
||||
use scroll_zkvm_types::{
|
||||
batch::{
|
||||
BatchHeader, BatchHeaderV6, BatchHeaderV7, BatchInfo, BatchWitness, Envelope, EnvelopeV6,
|
||||
EnvelopeV7, PointEvalWitness, ReferenceHeader, ToArchievedWitness, N_BLOB_BYTES,
|
||||
build_point_eval_witness, BatchHeader, BatchHeaderV6, BatchHeaderV7, BatchHeaderV8,
|
||||
BatchInfo, BatchWitness, Envelope, EnvelopeV6, EnvelopeV7, EnvelopeV8, LegacyBatchWitness,
|
||||
ReferenceHeader, N_BLOB_BYTES,
|
||||
},
|
||||
public_inputs::ForkName,
|
||||
task::ProvingTask,
|
||||
@@ -23,37 +24,35 @@ use utils::{base64, point_eval};
|
||||
#[serde(untagged)]
|
||||
pub enum BatchHeaderV {
|
||||
V6(BatchHeaderV6),
|
||||
V7(BatchHeaderV7),
|
||||
}
|
||||
|
||||
impl From<BatchHeaderV> for ReferenceHeader {
|
||||
fn from(value: BatchHeaderV) -> Self {
|
||||
match value {
|
||||
BatchHeaderV::V6(h) => ReferenceHeader::V6(h),
|
||||
BatchHeaderV::V7(h) => ReferenceHeader::V7(h),
|
||||
}
|
||||
}
|
||||
V7_8(BatchHeaderV7),
|
||||
}
|
||||
|
||||
impl BatchHeaderV {
|
||||
pub fn batch_hash(&self) -> B256 {
|
||||
match self {
|
||||
BatchHeaderV::V6(h) => h.batch_hash(),
|
||||
BatchHeaderV::V7(h) => h.batch_hash(),
|
||||
BatchHeaderV::V7_8(h) => h.batch_hash(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn must_v6_header(&self) -> &BatchHeaderV6 {
|
||||
match self {
|
||||
BatchHeaderV::V6(h) => h,
|
||||
BatchHeaderV::V7(_) => panic!("try to pick v7 header"),
|
||||
_ => panic!("try to pick other header type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn must_v7_header(&self) -> &BatchHeaderV7 {
|
||||
match self {
|
||||
BatchHeaderV::V7(h) => h,
|
||||
BatchHeaderV::V6(_) => panic!("try to pick v6 header"),
|
||||
BatchHeaderV::V7_8(h) => h,
|
||||
_ => panic!("try to pick other header type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn must_v8_header(&self) -> &BatchHeaderV8 {
|
||||
match self {
|
||||
BatchHeaderV::V7_8(h) => h,
|
||||
_ => panic!("try to pick other header type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +84,12 @@ impl TryFrom<BatchProvingTask> for ProvingTask {
|
||||
|
||||
fn try_from(value: BatchProvingTask) -> Result<Self> {
|
||||
let witness = value.build_guest_input();
|
||||
let serialized_witness = if crate::witness_use_legacy_mode() {
|
||||
let legacy_witness = LegacyBatchWitness::from(witness);
|
||||
to_rkyv_bytes::<RancorError>(&legacy_witness)?.into_vec()
|
||||
} else {
|
||||
super::encode_task_to_witness(&witness)?
|
||||
};
|
||||
|
||||
Ok(ProvingTask {
|
||||
identifier: value.batch_header.batch_hash().to_string(),
|
||||
@@ -92,9 +97,9 @@ impl TryFrom<BatchProvingTask> for ProvingTask {
|
||||
aggregated_proofs: value
|
||||
.chunk_proofs
|
||||
.into_iter()
|
||||
.map(|w_proof| w_proof.proof.into_root_proof().expect("expect root proof"))
|
||||
.map(|w_proof| w_proof.proof.into_stark_proof().expect("expect root proof"))
|
||||
.collect(),
|
||||
serialized_witness: vec![to_rkyv_bytes::<RancorError>(&witness)?.into_vec()],
|
||||
serialized_witness: vec![serialized_witness],
|
||||
vk: Vec::new(),
|
||||
})
|
||||
}
|
||||
@@ -120,20 +125,28 @@ impl BatchProvingTask {
|
||||
EnvelopeV6::from_slice(self.blob_bytes.as_slice())
|
||||
.challenge_digest(versioned_hash)
|
||||
}
|
||||
BatchHeaderV::V7(_) => {
|
||||
match fork_name {
|
||||
ForkName::EuclidV2 => (),
|
||||
_ => unreachable!("hardfork mismatch for da-codec@v6 header: found={fork_name:?}, expected={:?}",
|
||||
[ForkName::EuclidV2],
|
||||
),
|
||||
}
|
||||
BatchHeaderV::V7_8(_) => {
|
||||
let padded_blob_bytes = {
|
||||
let mut padded_blob_bytes = self.blob_bytes.to_vec();
|
||||
padded_blob_bytes.resize(N_BLOB_BYTES, 0);
|
||||
padded_blob_bytes
|
||||
};
|
||||
EnvelopeV7::from_slice(padded_blob_bytes.as_slice())
|
||||
.challenge_digest(versioned_hash)
|
||||
|
||||
match fork_name {
|
||||
ForkName::EuclidV2 => {
|
||||
<EnvelopeV7 as Envelope>::from_slice(padded_blob_bytes.as_slice())
|
||||
.challenge_digest(versioned_hash)
|
||||
}
|
||||
ForkName::Feynman => {
|
||||
<EnvelopeV8 as Envelope>::from_slice(padded_blob_bytes.as_slice())
|
||||
.challenge_digest(versioned_hash)
|
||||
}
|
||||
f => unreachable!(
|
||||
"hardfork mismatch for da-codec@v7 header: found={}, expected={:?}",
|
||||
f,
|
||||
[ForkName::EuclidV2, ForkName::Feynman],
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,12 +167,16 @@ impl BatchProvingTask {
|
||||
assert_eq!(p, kzg_proof);
|
||||
}
|
||||
|
||||
let point_eval_witness = PointEvalWitness {
|
||||
kzg_commitment: kzg_commitment.into_inner(),
|
||||
kzg_proof: kzg_proof.into_inner(),
|
||||
};
|
||||
let point_eval_witness = Some(build_point_eval_witness(
|
||||
kzg_commitment.into_inner(),
|
||||
kzg_proof.into_inner(),
|
||||
));
|
||||
|
||||
let reference_header = self.batch_header.clone().into();
|
||||
let reference_header = match fork_name {
|
||||
ForkName::EuclidV1 => ReferenceHeader::V6(*self.batch_header.must_v6_header()),
|
||||
ForkName::EuclidV2 => ReferenceHeader::V7(*self.batch_header.must_v7_header()),
|
||||
ForkName::Feynman => ReferenceHeader::V8(*self.batch_header.must_v8_header()),
|
||||
};
|
||||
|
||||
BatchWitness {
|
||||
fork_name,
|
||||
@@ -181,12 +198,7 @@ impl BatchProvingTask {
|
||||
// 1. generate data for metadata from the witness
|
||||
// 2. validate every adjacent proof pair
|
||||
let witness = self.build_guest_input();
|
||||
let archieved = ToArchievedWitness::create(&witness)
|
||||
.map_err(|e| eyre::eyre!("archieve batch witness fail: {e}"))?;
|
||||
let archieved_witness = archieved
|
||||
.access()
|
||||
.map_err(|e| eyre::eyre!("access archieved batch witness fail: {e}"))?;
|
||||
let metadata: BatchInfo = archieved_witness.into();
|
||||
let metadata = BatchInfo::from(&witness);
|
||||
|
||||
super::check_aggregation_proofs(self.chunk_proofs.as_slice(), fork_name)?;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ pub mod base64 {
|
||||
pub mod point_eval {
|
||||
use c_kzg;
|
||||
use sbv_primitives::{types::eips::eip4844::BLS_MODULUS, B256 as H256, U256};
|
||||
use scroll_zkvm_types::util::sha256_rv32;
|
||||
use scroll_zkvm_types::utils::sha256_rv32;
|
||||
|
||||
/// Given the blob-envelope, translate it to a fixed size EIP-4844 blob.
|
||||
///
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::proofs::BatchProof;
|
||||
use eyre::Result;
|
||||
use scroll_zkvm_types::{
|
||||
bundle::{BundleInfo, BundleWitness, ToArchievedWitness},
|
||||
bundle::{BundleInfo, BundleWitness},
|
||||
public_inputs::ForkName,
|
||||
task::ProvingTask,
|
||||
utils::{to_rkyv_bytes, RancorError},
|
||||
};
|
||||
|
||||
/// Message indicating a sanity check failure.
|
||||
@@ -56,12 +57,7 @@ impl BundleProvingTask {
|
||||
// 1. generate data for metadata from the witness
|
||||
// 2. validate every adjacent proof pair
|
||||
let witness = self.build_guest_input();
|
||||
let archieved = ToArchievedWitness::create(&witness)
|
||||
.map_err(|e| eyre::eyre!("archieve bundle witness fail: {e}"))?;
|
||||
let archieved_witness = archieved
|
||||
.access()
|
||||
.map_err(|e| eyre::eyre!("access archieved bundle witness fail: {e}"))?;
|
||||
let metadata: BundleInfo = archieved_witness.into();
|
||||
let metadata = BundleInfo::from(&witness);
|
||||
|
||||
super::check_aggregation_proofs(self.batch_proofs.as_slice(), fork_name)?;
|
||||
|
||||
@@ -74,6 +70,11 @@ impl TryFrom<BundleProvingTask> for ProvingTask {
|
||||
|
||||
fn try_from(value: BundleProvingTask) -> Result<Self> {
|
||||
let witness = value.build_guest_input();
|
||||
let serialized_witness = if crate::witness_use_legacy_mode() {
|
||||
to_rkyv_bytes::<RancorError>(&witness)?.into_vec()
|
||||
} else {
|
||||
super::encode_task_to_witness(&witness)?
|
||||
};
|
||||
|
||||
Ok(ProvingTask {
|
||||
identifier: value.identifier(),
|
||||
@@ -81,9 +82,9 @@ impl TryFrom<BundleProvingTask> for ProvingTask {
|
||||
aggregated_proofs: value
|
||||
.batch_proofs
|
||||
.into_iter()
|
||||
.map(|w_proof| w_proof.proof.into_root_proof().expect("expect root proof"))
|
||||
.map(|w_proof| w_proof.proof.into_stark_proof().expect("expect root proof"))
|
||||
.collect(),
|
||||
serialized_witness: vec![witness.rkyv_serialize(None)?.to_vec()],
|
||||
serialized_witness: vec![serialized_witness],
|
||||
vk: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::chunk_interpreter::*;
|
||||
use eyre::Result;
|
||||
use sbv_primitives::{types::BlockWitness, B256};
|
||||
use sbv_core::BlockWitness;
|
||||
use sbv_primitives::B256;
|
||||
use scroll_zkvm_types::{
|
||||
chunk::{execute, ChunkInfo, ChunkWitness, ToArchievedWitness},
|
||||
chunk::{execute, ChunkInfo, ChunkWitness, LegacyChunkWitness},
|
||||
task::ProvingTask,
|
||||
utils::{to_rkyv_bytes, RancorError},
|
||||
};
|
||||
|
||||
/// The type aligned with coordinator's defination
|
||||
@@ -66,12 +68,18 @@ impl TryFrom<ChunkProvingTask> for ProvingTask {
|
||||
|
||||
fn try_from(value: ChunkProvingTask) -> Result<Self> {
|
||||
let witness = value.build_guest_input();
|
||||
let serialized_witness = if crate::witness_use_legacy_mode() {
|
||||
let legacy_witness = LegacyChunkWitness::from(witness);
|
||||
to_rkyv_bytes::<RancorError>(&legacy_witness)?.into_vec()
|
||||
} else {
|
||||
super::encode_task_to_witness(&witness)?
|
||||
};
|
||||
|
||||
Ok(ProvingTask {
|
||||
identifier: value.identifier(),
|
||||
fork_name: value.fork_name,
|
||||
aggregated_proofs: Vec::new(),
|
||||
serialized_witness: vec![witness.rkyv_serialize(None)?.to_vec()],
|
||||
serialized_witness: vec![serialized_witness],
|
||||
vk: Vec::new(),
|
||||
})
|
||||
}
|
||||
@@ -83,7 +91,7 @@ impl ChunkProvingTask {
|
||||
let num_txs = self
|
||||
.block_witnesses
|
||||
.iter()
|
||||
.map(|b| b.transaction.len())
|
||||
.map(|b| b.transactions.len())
|
||||
.sum::<usize>();
|
||||
let total_gas_used = self
|
||||
.block_witnesses
|
||||
@@ -131,18 +139,14 @@ impl ChunkProvingTask {
|
||||
|
||||
pub fn precheck_and_build_metadata(&self) -> Result<ChunkInfo> {
|
||||
let witness = self.build_guest_input();
|
||||
let archieved = ToArchievedWitness::create(&witness)
|
||||
.map_err(|e| eyre::eyre!("archieve chunk witness fail: {e}"))?;
|
||||
let archieved_witness = archieved
|
||||
.access()
|
||||
.map_err(|e| eyre::eyre!("access archieved chunk witness fail: {e}"))?;
|
||||
|
||||
let ret = ChunkInfo::try_from(archieved_witness).map_err(|e| eyre::eyre!("{e}"))?;
|
||||
let ret = ChunkInfo::try_from(witness).map_err(|e| eyre::eyre!("{e}"))?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// this method check the validate of current task (there may be missing storage node)
|
||||
/// and try fixing it until everything is ok
|
||||
#[deprecated]
|
||||
pub fn prepare_task_via_interpret(
|
||||
&mut self,
|
||||
interpreter: impl ChunkInterpreter,
|
||||
@@ -166,13 +170,8 @@ impl ChunkProvingTask {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
let witness = self.build_guest_input();
|
||||
let archieved = ToArchievedWitness::create(&witness)
|
||||
.map_err(|e| eyre::eyre!("archieve chunk witness fail: {e}"))?;
|
||||
let archieved_witness = archieved
|
||||
.access()
|
||||
.map_err(|e| eyre::eyre!("access archieved chunk witness fail: {e}"))?;
|
||||
|
||||
match execute(archieved_witness) {
|
||||
match execute(witness) {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => {
|
||||
if let Some(caps) = err_parse_re.captures(&e) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use eyre::Result;
|
||||
use sbv_primitives::{types::BlockWitness, Bytes, B256};
|
||||
use sbv_core::BlockWitness;
|
||||
use sbv_primitives::{Bytes, B256};
|
||||
|
||||
/// An interpreter which is cirtical in translating chunk data
|
||||
/// since we need to grep block witness and storage node data
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(static_mut_refs)]
|
||||
|
||||
mod euclidv2;
|
||||
use euclidv2::EuclidV2Verifier;
|
||||
mod universal;
|
||||
use eyre::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -9,6 +8,7 @@ use std::{
|
||||
path::Path,
|
||||
sync::{Arc, Mutex, OnceLock},
|
||||
};
|
||||
use universal::Verifier;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TaskType {
|
||||
@@ -61,7 +61,7 @@ pub fn init(config: VerifierConfig) {
|
||||
for cfg in &config.circuits {
|
||||
let canonical_fork_name = cfg.fork_name.to_lowercase();
|
||||
|
||||
let verifier = EuclidV2Verifier::new(&cfg.assets_path, canonical_fork_name.as_str().into());
|
||||
let verifier = Verifier::new(&cfg.assets_path, canonical_fork_name.as_str().into());
|
||||
let ret = verifiers.insert(canonical_fork_name, Arc::new(Mutex::new(verifier)));
|
||||
assert!(
|
||||
ret.is_none(),
|
||||
|
||||
@@ -7,59 +7,51 @@ use crate::{
|
||||
utils::panic_catch,
|
||||
};
|
||||
use scroll_zkvm_types::public_inputs::ForkName;
|
||||
use scroll_zkvm_verifier_euclid::verifier::UniversalVerifier;
|
||||
use scroll_zkvm_verifier::verifier::UniversalVerifier;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct EuclidV2Verifier {
|
||||
pub struct Verifier {
|
||||
verifier: UniversalVerifier,
|
||||
fork: ForkName,
|
||||
}
|
||||
|
||||
impl EuclidV2Verifier {
|
||||
impl Verifier {
|
||||
pub fn new(assets_dir: &str, fork: ForkName) -> Self {
|
||||
let verifier_bin = Path::new(assets_dir).join("verifier.bin");
|
||||
let config = Path::new(assets_dir).join("root-verifier-vm-config");
|
||||
let exe = Path::new(assets_dir).join("root-verifier-committed-exe");
|
||||
let verifier_bin = Path::new(assets_dir);
|
||||
|
||||
Self {
|
||||
verifier: UniversalVerifier::setup(&config, &exe, &verifier_bin)
|
||||
.expect("Setting up chunk verifier"),
|
||||
verifier: UniversalVerifier::setup(verifier_bin).expect("Setting up chunk verifier"),
|
||||
fork,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProofVerifier for EuclidV2Verifier {
|
||||
impl ProofVerifier for Verifier {
|
||||
fn verify(&self, task_type: super::TaskType, proof: &[u8]) -> Result<bool> {
|
||||
panic_catch(|| match task_type {
|
||||
TaskType::Chunk => {
|
||||
let proof = serde_json::from_slice::<ChunkProof>(proof).unwrap();
|
||||
if !proof.pi_hash_check(self.fork) {
|
||||
return false;
|
||||
}
|
||||
assert!(proof.pi_hash_check(self.fork));
|
||||
self.verifier
|
||||
.verify_proof(proof.as_root_proof(), &proof.vk)
|
||||
.verify_stark_proof(proof.as_root_proof(), &proof.vk)
|
||||
.unwrap()
|
||||
}
|
||||
TaskType::Batch => {
|
||||
let proof = serde_json::from_slice::<BatchProof>(proof).unwrap();
|
||||
if !proof.pi_hash_check(self.fork) {
|
||||
return false;
|
||||
}
|
||||
assert!(proof.pi_hash_check(self.fork));
|
||||
self.verifier
|
||||
.verify_proof(proof.as_root_proof(), &proof.vk)
|
||||
.verify_stark_proof(proof.as_root_proof(), &proof.vk)
|
||||
.unwrap()
|
||||
}
|
||||
TaskType::Bundle => {
|
||||
let proof = serde_json::from_slice::<BundleProof>(proof).unwrap();
|
||||
if !proof.pi_hash_check(self.fork) {
|
||||
return false;
|
||||
}
|
||||
assert!(proof.pi_hash_check(self.fork));
|
||||
let vk = proof.vk.clone();
|
||||
let evm_proof = proof.into_evm_proof();
|
||||
self.verifier.verify_proof_evm(&evm_proof, &vk).unwrap()
|
||||
self.verifier.verify_evm_proof(&evm_proof, &vk).unwrap()
|
||||
}
|
||||
})
|
||||
.map(|_| true)
|
||||
.map_err(|err_str: String| eyre::eyre!("{err_str}"))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,16 @@ use std::sync::OnceLock;
|
||||
|
||||
static LOG_SETTINGS: OnceLock<Result<(), String>> = OnceLock::new();
|
||||
|
||||
fn enable_dump() -> bool {
|
||||
static ZKVM_DEBUG_DUMP: OnceLock<bool> = OnceLock::new();
|
||||
*ZKVM_DEBUG_DUMP.get_or_init(|| {
|
||||
std::env::var("ZKVM_DEBUG")
|
||||
.or_else(|_| std::env::var("ZKVM_DEBUG_PROOF"))
|
||||
.map(|s| s.to_lowercase() == "true")
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn init_tracing() {
|
||||
@@ -52,6 +62,7 @@ pub unsafe extern "C" fn init_l2geth(config: *const c_char) {
|
||||
|
||||
fn verify_proof(proof: *const c_char, fork_name: *const c_char, task_type: TaskType) -> c_char {
|
||||
let fork_name_str = c_char_to_str(fork_name);
|
||||
let proof_str = proof;
|
||||
let proof = c_char_to_vec(proof);
|
||||
|
||||
match libzkp::verify_proof(proof, fork_name_str, task_type) {
|
||||
@@ -59,7 +70,24 @@ fn verify_proof(proof: *const c_char, fork_name: *const c_char, task_type: TaskT
|
||||
tracing::error!("{:?} verify failed, error: {:#}", task_type, e);
|
||||
false as c_char
|
||||
}
|
||||
Ok(result) => result as c_char,
|
||||
Ok(result) => {
|
||||
if !result && enable_dump() {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
// Dump req.input to a temporary file
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let filename = format!("/tmp/proof_{}.json", timestamp);
|
||||
let cstr = unsafe { std::ffi::CStr::from_ptr(proof_str) };
|
||||
if let Err(e) = std::fs::write(&filename, cstr.to_bytes()) {
|
||||
eprintln!("Failed to write proof to file {}: {}", filename, e);
|
||||
} else {
|
||||
println!("Dumped failed proof to {}", filename);
|
||||
}
|
||||
}
|
||||
result as c_char
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,17 +153,12 @@ pub unsafe extern "C" fn gen_universal_task(
|
||||
expected_vk: *const u8,
|
||||
expected_vk_len: usize,
|
||||
) -> HandlingResult {
|
||||
let mut interpreter = None;
|
||||
let task_json = if task_type == TaskType::Chunk as i32 {
|
||||
let pre_task_str = c_char_to_str(task);
|
||||
let cli = l2geth::get_client();
|
||||
match libzkp::checkout_chunk_task(pre_task_str, cli) {
|
||||
Ok(str) => {
|
||||
interpreter.replace(cli);
|
||||
str
|
||||
}
|
||||
Ok(str) => str,
|
||||
Err(e) => {
|
||||
println!("gen_universal_task failed at pre interpret step, error: {e}");
|
||||
tracing::error!("gen_universal_task failed at pre interpret step, error: {e}");
|
||||
return failed_handling_result();
|
||||
}
|
||||
@@ -150,13 +173,8 @@ pub unsafe extern "C" fn gen_universal_task(
|
||||
&[]
|
||||
};
|
||||
|
||||
let ret = libzkp::gen_universal_task(
|
||||
task_type,
|
||||
&task_json,
|
||||
c_char_to_str(fork_name),
|
||||
expected_vk,
|
||||
interpreter,
|
||||
);
|
||||
let ret =
|
||||
libzkp::gen_universal_task(task_type, &task_json, c_char_to_str(fork_name), expected_vk);
|
||||
|
||||
if let Ok((pi_hash, meta_json, task_json)) = ret {
|
||||
let expected_pi_hash = pi_hash.0.map(|byte| byte as c_char);
|
||||
@@ -167,6 +185,22 @@ pub unsafe extern "C" fn gen_universal_task(
|
||||
expected_pi_hash,
|
||||
}
|
||||
} else {
|
||||
if enable_dump() {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
// Dump req.input to a temporary file
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let c_str = unsafe { std::ffi::CStr::from_ptr(fork_name) };
|
||||
let filename = format!("/tmp/task_{}_{}.json", c_str.to_str().unwrap(), timestamp);
|
||||
if let Err(e) = std::fs::write(&filename, task_json.as_bytes()) {
|
||||
eprintln!("Failed to write task to file {}: {}", filename, e);
|
||||
} else {
|
||||
println!("Dumped failed task to {}", filename);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::error!("gen_universal_task failed, error: {:#}", ret.unwrap_err());
|
||||
failed_handling_result()
|
||||
}
|
||||
@@ -211,3 +245,10 @@ pub unsafe extern "C" fn release_string(ptr: *mut c_char) {
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_dynamic_feature(feats: *const c_char) {
|
||||
let feats_str = c_char_to_str(feats);
|
||||
libzkp::set_dynamic_feature(feats_str);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
scroll-zkvm-types.workspace = true
|
||||
scroll-zkvm-prover-euclid.workspace = true
|
||||
scroll-proving-sdk = { git = "https://github.com/scroll-tech/scroll-proving-sdk.git", branch = "refactor/scroll" }
|
||||
scroll-zkvm-prover.workspace = true
|
||||
scroll-proving-sdk = { git = "https://github.com/scroll-tech/scroll-proving-sdk.git", rev = "4c36ab2" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
once_cell.workspace =true
|
||||
@@ -17,8 +17,9 @@ tiny-keccak = { workspace = true, features = ["sha3", "keccak"] }
|
||||
eyre.workspace = true
|
||||
|
||||
futures = "0.3.30"
|
||||
futures-util = "0.3"
|
||||
|
||||
reqwest = { version = "0.12.4", features = ["gzip"] }
|
||||
reqwest = { version = "0.12.4", features = ["gzip", "stream"] }
|
||||
reqwest-middleware = "0.3"
|
||||
reqwest-retry = "0.5"
|
||||
hex = "0.4.3"
|
||||
@@ -30,5 +31,9 @@ sled = "0.34.7"
|
||||
http = "1.1.0"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
ctor = "0.2.8"
|
||||
url = "2.5.4"
|
||||
url = { version = "2.5.4", features = ["serde",] }
|
||||
serde_bytes = "0.11.15"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
cuda = ["scroll-zkvm-prover/cuda"]
|
||||
7
crates/prover-bin/assets_url_preset.json
Normal file
7
crates/prover-bin/assets_url_preset.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"feynman": {
|
||||
"b68fdc3f28a5ce006280980df70cd3447e56913e5bca6054603ba85f0794c23a6618ea25a7991845bbc5fd571670ee47379ba31ace92d345bca59702a0d4112d": "https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/0.5.2/chunk/",
|
||||
"9a3f66370f11e3303f1a1248921025104e83253efea43a70d221cf4e15fc145bf2be2f4468d1ac4a70e7682babb1c60417e21c7633d4b55b58f44703ec82b05a": "https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/0.5.2/batch/",
|
||||
"1f8627277e1c1f6e1cc70c03e6fde06929e5ea27ca5b1d56e23b235dfeda282e22c0e5294bcb1b3a9def836f8d0f18612a9860629b9497292976ca11844b7e73": "https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/0.5.2/bundle/"
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,10 @@ mod zk_circuits_handler;
|
||||
use clap::{ArgAction, Parser, Subcommand};
|
||||
use prover::{LocalProver, LocalProverConfig};
|
||||
use scroll_proving_sdk::{
|
||||
prover::ProverBuilder,
|
||||
prover::{types::ProofType, ProverBuilder},
|
||||
utils::{get_version, init_tracing},
|
||||
};
|
||||
use std::{fs::File, io::BufReader, path::Path};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(disable_version_flag = true)]
|
||||
@@ -33,13 +34,19 @@ struct Args {
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// Dump vk of this prover
|
||||
Dump {
|
||||
Handle {
|
||||
/// path to save the verifier's asset
|
||||
asset_path: String,
|
||||
task_path: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct HandleSet {
|
||||
chunks: Vec<String>,
|
||||
batches: Vec<String>,
|
||||
bundles: Vec<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
init_tracing();
|
||||
@@ -52,15 +59,43 @@ async fn main() -> eyre::Result<()> {
|
||||
}
|
||||
|
||||
let cfg = LocalProverConfig::from_file(args.config_file)?;
|
||||
let default_fork_name = cfg.circuits.keys().next().unwrap().clone();
|
||||
let sdk_config = cfg.sdk_config.clone();
|
||||
let local_prover = LocalProver::new(cfg.clone());
|
||||
|
||||
match args.command {
|
||||
Some(Commands::Dump { asset_path }) => {
|
||||
let fork_name = args.fork_name.unwrap_or(default_fork_name);
|
||||
println!("dump assets for {fork_name} into {asset_path}");
|
||||
local_prover.dump_verifier_assets(&fork_name, asset_path.as_ref())?;
|
||||
Some(Commands::Handle { task_path }) => {
|
||||
let file = File::open(Path::new(&task_path))?;
|
||||
let reader = BufReader::new(file);
|
||||
let handle_set: HandleSet = serde_json::from_reader(reader)?;
|
||||
|
||||
let prover = ProverBuilder::new(sdk_config, local_prover)
|
||||
.build()
|
||||
.await
|
||||
.map_err(|e| eyre::eyre!("build prover fail: {e}"))?;
|
||||
|
||||
let prover = std::sync::Arc::new(prover);
|
||||
println!("Handling task set 1: chunks ...");
|
||||
assert!(
|
||||
prover
|
||||
.clone()
|
||||
.one_shot(&handle_set.chunks, ProofType::Chunk)
|
||||
.await
|
||||
);
|
||||
println!("Done! Handling task set 2: batches ...");
|
||||
assert!(
|
||||
prover
|
||||
.clone()
|
||||
.one_shot(&handle_set.batches, ProofType::Batch)
|
||||
.await
|
||||
);
|
||||
println!("Done! Handling task set 3: bundles ...");
|
||||
assert!(
|
||||
prover
|
||||
.clone()
|
||||
.one_shot(&handle_set.bundles, ProofType::Bundle)
|
||||
.await
|
||||
);
|
||||
println!("All done!");
|
||||
}
|
||||
None => {
|
||||
let prover = ProverBuilder::new(sdk_config, local_prover)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::zk_circuits_handler::{euclidV2::EuclidV2Handler, CircuitsHandler};
|
||||
use crate::zk_circuits_handler::{universal::UniversalHandler, CircuitsHandler};
|
||||
use async_trait::async_trait;
|
||||
use eyre::Result;
|
||||
use scroll_proving_sdk::{
|
||||
@@ -16,12 +16,111 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
path::Path,
|
||||
sync::{Arc, OnceLock},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, LazyLock},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use tokio::{runtime::Handle, sync::Mutex, task::JoinHandle};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct AssetsLocationData {
|
||||
/// the base url to form a general downloading url for an asset, MUST HAVE A TRAILING SLASH
|
||||
pub base_url: url::Url,
|
||||
#[serde(default)]
|
||||
/// a altered url for specififed vk
|
||||
pub asset_detours: HashMap<String, url::Url>,
|
||||
}
|
||||
|
||||
impl AssetsLocationData {
|
||||
pub fn gen_asset_url(&self, vk_as_path: &str, proof_type: ProofType) -> Result<url::Url> {
|
||||
Ok(self.base_url.join(
|
||||
match proof_type {
|
||||
ProofType::Chunk => format!("chunk/{vk_as_path}/"),
|
||||
ProofType::Batch => format!("batch/{vk_as_path}/"),
|
||||
ProofType::Bundle => format!("bundle/{vk_as_path}/"),
|
||||
t => eyre::bail!("unrecognized proof type: {}", t as u8),
|
||||
}
|
||||
.as_str(),
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if !self.base_url.path().ends_with('/') {
|
||||
eyre::bail!(
|
||||
"base_url must have a trailing slash, got: {}",
|
||||
self.base_url
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_asset(
|
||||
&self,
|
||||
vk: &str,
|
||||
url_base: &url::Url,
|
||||
base_path: impl AsRef<Path>,
|
||||
) -> Result<PathBuf> {
|
||||
let download_files = ["app.vmexe", "openvm.toml"];
|
||||
|
||||
// Step 1: Create a local path for storage
|
||||
let storage_path = base_path.as_ref().join(vk);
|
||||
std::fs::create_dir_all(&storage_path)?;
|
||||
|
||||
// Step 2 & 3: Download each file if needed
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
for filename in download_files.iter() {
|
||||
let local_file_path = storage_path.join(filename);
|
||||
let download_url = url_base.join(filename)?;
|
||||
|
||||
// Check if file already exists
|
||||
if local_file_path.exists() {
|
||||
// Get file metadata to check size
|
||||
if let Ok(metadata) = std::fs::metadata(&local_file_path) {
|
||||
// Make a HEAD request to get remote file size
|
||||
|
||||
if let Ok(head_resp) = client.head(download_url.clone()).send().await {
|
||||
if let Some(content_length) = head_resp.headers().get("content-length") {
|
||||
if let Ok(remote_size) =
|
||||
content_length.to_str().unwrap_or("0").parse::<u64>()
|
||||
{
|
||||
// If sizes match, skip download
|
||||
if metadata.len() == remote_size {
|
||||
println!("File {} already exists with matching size, skipping download", filename);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Downloading {} from {}", filename, download_url);
|
||||
|
||||
let response = client.get(download_url).send().await?;
|
||||
if !response.status().is_success() {
|
||||
eyre::bail!(
|
||||
"Failed to download {}: HTTP status {}",
|
||||
filename,
|
||||
response.status()
|
||||
);
|
||||
}
|
||||
|
||||
// Stream the content directly to file instead of loading into memory
|
||||
let mut file = std::fs::File::create(&local_file_path)?;
|
||||
let mut stream = response.bytes_stream();
|
||||
|
||||
use futures_util::StreamExt;
|
||||
while let Some(chunk) = stream.next().await {
|
||||
std::io::Write::write_all(&mut file, &chunk?)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Return the storage path
|
||||
Ok(storage_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct LocalProverConfig {
|
||||
pub sdk_config: SdkConfig,
|
||||
@@ -45,7 +144,11 @@ impl LocalProverConfig {
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct CircuitConfig {
|
||||
pub hard_fork_name: String,
|
||||
/// The path to save assets for a specified hard fork phase
|
||||
pub workspace_path: String,
|
||||
#[serde(flatten)]
|
||||
/// The location data for dynamic loading
|
||||
pub location_data: AssetsLocationData,
|
||||
/// cached vk value to save some initial cost, for debugging only
|
||||
#[serde(default)]
|
||||
pub vks: HashMap<ProofType, String>,
|
||||
@@ -56,7 +159,7 @@ pub struct LocalProver {
|
||||
next_task_id: u64,
|
||||
current_task: Option<JoinHandle<Result<String>>>,
|
||||
|
||||
handlers: HashMap<String, OnceLock<Arc<dyn CircuitsHandler>>>,
|
||||
handlers: HashMap<String, Arc<dyn CircuitsHandler>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -64,24 +167,15 @@ impl ProvingService for LocalProver {
|
||||
fn is_local(&self) -> bool {
|
||||
true
|
||||
}
|
||||
async fn get_vks(&self, req: GetVkRequest) -> GetVkResponse {
|
||||
let mut vks = vec![];
|
||||
for (hard_fork_name, cfg) in self.config.circuits.iter() {
|
||||
for proof_type in &req.proof_types {
|
||||
if let Some(vk) = cfg.vks.get(proof_type) {
|
||||
vks.push(vk.clone())
|
||||
} else {
|
||||
let handler = self.get_or_init_handler(hard_fork_name);
|
||||
vks.push(handler.get_vk(*proof_type).await);
|
||||
}
|
||||
}
|
||||
async fn get_vks(&self, _: GetVkRequest) -> GetVkResponse {
|
||||
// get vk has been deprecated in new prover with dynamic asset loading scheme
|
||||
GetVkResponse {
|
||||
vks: vec![],
|
||||
error: None,
|
||||
}
|
||||
|
||||
GetVkResponse { vks, error: None }
|
||||
}
|
||||
async fn prove(&mut self, req: ProveRequest) -> ProveResponse {
|
||||
let handler = self.get_or_init_handler(&req.hard_fork_name);
|
||||
match self.do_prove(req, handler).await {
|
||||
match self.do_prove(req).await {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => ProveResponse {
|
||||
status: TaskStatus::Failed,
|
||||
@@ -132,34 +226,91 @@ impl ProvingService for LocalProver {
|
||||
}
|
||||
}
|
||||
|
||||
static GLOBAL_ASSET_URLS: LazyLock<HashMap<String, HashMap<String, url::Url>>> =
|
||||
LazyLock::new(|| {
|
||||
const ASSETS_JSON: &str = include_str!("../assets_url_preset.json");
|
||||
serde_json::from_str(ASSETS_JSON).expect("Failed to parse assets_url_preset.json")
|
||||
});
|
||||
|
||||
impl LocalProver {
|
||||
pub fn new(config: LocalProverConfig) -> Self {
|
||||
let handlers = config
|
||||
.circuits
|
||||
.keys()
|
||||
.map(|k| (k.clone(), OnceLock::new()))
|
||||
.collect();
|
||||
pub fn new(mut config: LocalProverConfig) -> Self {
|
||||
for (fork_name, circuit_config) in config.circuits.iter_mut() {
|
||||
// validate each base url
|
||||
circuit_config.location_data.validate().unwrap();
|
||||
let mut template_url_mapping = GLOBAL_ASSET_URLS
|
||||
.get(&fork_name.to_lowercase())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
// apply default settings in template
|
||||
for (key, url) in circuit_config.location_data.asset_detours.drain() {
|
||||
template_url_mapping.insert(key, url);
|
||||
}
|
||||
|
||||
circuit_config.location_data.asset_detours = template_url_mapping;
|
||||
|
||||
// validate each detours url
|
||||
for url in circuit_config.location_data.asset_detours.values() {
|
||||
assert!(
|
||||
url.path().ends_with('/'),
|
||||
"url {} must be end with /",
|
||||
url.as_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
config,
|
||||
next_task_id: 0,
|
||||
current_task: None,
|
||||
handlers,
|
||||
handlers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_prove(
|
||||
&mut self,
|
||||
req: ProveRequest,
|
||||
handler: Arc<dyn CircuitsHandler>,
|
||||
) -> Result<ProveResponse> {
|
||||
async fn do_prove(&mut self, req: ProveRequest) -> Result<ProveResponse> {
|
||||
self.next_task_id += 1;
|
||||
let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
let created_at = duration.as_secs() as f64 + duration.subsec_nanos() as f64 * 1e-9;
|
||||
|
||||
let req_clone = req.clone();
|
||||
let prover_task = UniversalHandler::get_task_from_input(&req.input)?;
|
||||
let vk = hex::encode(&prover_task.vk);
|
||||
let handler = if let Some(handler) = self.handlers.get(&vk) {
|
||||
handler.clone()
|
||||
} else {
|
||||
let base_config = self
|
||||
.config
|
||||
.circuits
|
||||
.get(&req.hard_fork_name)
|
||||
.ok_or_else(|| {
|
||||
eyre::eyre!(
|
||||
"coordinator sent unexpected forkname {}",
|
||||
req.hard_fork_name
|
||||
)
|
||||
})?;
|
||||
let url_base = if let Some(url) = base_config.location_data.asset_detours.get(&vk) {
|
||||
url.clone()
|
||||
} else {
|
||||
base_config
|
||||
.location_data
|
||||
.gen_asset_url(&vk, req.proof_type)?
|
||||
};
|
||||
let asset_path = base_config
|
||||
.location_data
|
||||
.get_asset(&vk, &url_base, &base_config.workspace_path)
|
||||
.await?;
|
||||
let circuits_handler = Arc::new(Mutex::new(UniversalHandler::new(
|
||||
&asset_path,
|
||||
req.proof_type,
|
||||
)?));
|
||||
self.handlers.insert(vk, circuits_handler.clone());
|
||||
circuits_handler
|
||||
};
|
||||
|
||||
let handle = Handle::current();
|
||||
let task_handle =
|
||||
tokio::task::spawn_blocking(move || handle.block_on(handler.get_proof_data(req_clone)));
|
||||
let is_evm = req.proof_type == ProofType::Bundle;
|
||||
let task_handle = tokio::task::spawn_blocking(move || {
|
||||
handle.block_on(handler.get_proof_data(&prover_task, is_evm))
|
||||
});
|
||||
self.current_task = Some(task_handle);
|
||||
|
||||
Ok(ProveResponse {
|
||||
@@ -173,73 +324,4 @@ impl LocalProver {
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_or_init_handler(&self, hard_fork_name: &str) -> Arc<dyn CircuitsHandler> {
|
||||
let lk = self
|
||||
.handlers
|
||||
.get(hard_fork_name)
|
||||
.expect("coordinator should never sent unexpected forkname");
|
||||
lk.get_or_init(|| self.new_handler(hard_fork_name)).clone()
|
||||
}
|
||||
|
||||
pub fn new_handler(&self, hard_fork_name: &str) -> Arc<dyn CircuitsHandler> {
|
||||
// if we got assigned a task for an unknown hard fork, there is something wrong in the
|
||||
// coordinator
|
||||
let config = self.config.circuits.get(hard_fork_name).unwrap();
|
||||
|
||||
match hard_fork_name {
|
||||
// The new EuclidV2Handler is a universal handler
|
||||
// We can add other handler implements if needed
|
||||
"some future forkname" => unreachable!(),
|
||||
_ => Arc::new(Arc::new(Mutex::new(EuclidV2Handler::new(config))))
|
||||
as Arc<dyn CircuitsHandler>,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_verifier_assets(&self, hard_fork_name: &str, out_path: &Path) -> Result<()> {
|
||||
let config = self
|
||||
.config
|
||||
.circuits
|
||||
.get(hard_fork_name)
|
||||
.ok_or_else(|| eyre::eyre!("no corresponding config for fork {hard_fork_name}"))?;
|
||||
|
||||
let workspace_path = &config.workspace_path;
|
||||
let universal_prover = EuclidV2Handler::new(config);
|
||||
let _ = universal_prover
|
||||
.get_prover()
|
||||
.dump_universal_verifier(Some(out_path))?;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct VKDump {
|
||||
pub chunk_vk: String,
|
||||
pub batch_vk: String,
|
||||
pub bundle_vk: String,
|
||||
}
|
||||
|
||||
let dump = VKDump {
|
||||
chunk_vk: universal_prover.get_vk_and_cache(ProofType::Chunk),
|
||||
batch_vk: universal_prover.get_vk_and_cache(ProofType::Batch),
|
||||
bundle_vk: universal_prover.get_vk_and_cache(ProofType::Bundle),
|
||||
};
|
||||
|
||||
let f = File::create(out_path.join("openVmVk.json"))?;
|
||||
serde_json::to_writer(f, &dump)?;
|
||||
|
||||
// Copy verifier.bin from workspace bundle directory to output path
|
||||
let bundle_verifier_path = Path::new(workspace_path)
|
||||
.join("bundle")
|
||||
.join("verifier.bin");
|
||||
if bundle_verifier_path.exists() {
|
||||
let dest_path = out_path.join("verifier.bin");
|
||||
std::fs::copy(&bundle_verifier_path, &dest_path)
|
||||
.map_err(|e| eyre::eyre!("Failed to copy verifier.bin: {}", e))?;
|
||||
} else {
|
||||
eprintln!(
|
||||
"Warning: verifier.bin not found at {:?}",
|
||||
bundle_verifier_path
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
|
||||
@@ -1,65 +1,13 @@
|
||||
//pub mod euclid;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub mod euclidV2;
|
||||
pub mod universal;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use eyre::Result;
|
||||
use scroll_proving_sdk::prover::{proving_service::ProveRequest, ProofType};
|
||||
use scroll_zkvm_prover_euclid::ProverConfig;
|
||||
use std::path::Path;
|
||||
use scroll_zkvm_types::ProvingTask;
|
||||
|
||||
#[async_trait]
|
||||
pub trait CircuitsHandler: Sync + Send {
|
||||
async fn get_vk(&self, task_type: ProofType) -> String;
|
||||
|
||||
async fn get_proof_data(&self, prove_request: ProveRequest) -> Result<String>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum Phase {
|
||||
EuclidV2,
|
||||
}
|
||||
|
||||
impl Phase {
|
||||
pub fn phase_spec_chunk(&self, workspace_path: &Path) -> ProverConfig {
|
||||
let dir_cache = Some(workspace_path.join("cache"));
|
||||
let path_app_exe = workspace_path.join("chunk/app.vmexe");
|
||||
let path_app_config = workspace_path.join("chunk/openvm.toml");
|
||||
let segment_len = Some((1 << 22) - 100);
|
||||
ProverConfig {
|
||||
dir_cache,
|
||||
path_app_config,
|
||||
path_app_exe,
|
||||
segment_len,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn phase_spec_batch(&self, workspace_path: &Path) -> ProverConfig {
|
||||
let dir_cache = Some(workspace_path.join("cache"));
|
||||
let path_app_exe = workspace_path.join("batch/app.vmexe");
|
||||
let path_app_config = workspace_path.join("batch/openvm.toml");
|
||||
let segment_len = Some((1 << 22) - 100);
|
||||
ProverConfig {
|
||||
dir_cache,
|
||||
path_app_config,
|
||||
path_app_exe,
|
||||
segment_len,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn phase_spec_bundle(&self, workspace_path: &Path) -> ProverConfig {
|
||||
let dir_cache = Some(workspace_path.join("cache"));
|
||||
let path_app_config = workspace_path.join("bundle/openvm.toml");
|
||||
let segment_len = Some((1 << 22) - 100);
|
||||
ProverConfig {
|
||||
dir_cache,
|
||||
path_app_config,
|
||||
segment_len,
|
||||
path_app_exe: workspace_path.join("bundle/app.vmexe"),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
async fn get_proof_data(&self, u_task: &ProvingTask, need_snark: bool) -> Result<String>;
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use super::CircuitsHandler;
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use scroll_proving_sdk::prover::{proving_service::ProveRequest, ProofType};
|
||||
use scroll_zkvm_prover_euclid::{
|
||||
task::{batch::BatchProvingTask, bundle::BundleProvingTask, chunk::ChunkProvingTask},
|
||||
BatchProver, BundleProverEuclidV1, ChunkProver, ProverConfig,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
pub struct EuclidHandler {
|
||||
chunk_prover: ChunkProver,
|
||||
batch_prover: BatchProver,
|
||||
bundle_prover: BundleProverEuclidV1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum Phase {
|
||||
EuclidV1,
|
||||
EuclidV2,
|
||||
}
|
||||
|
||||
impl Phase {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Phase::EuclidV1 => "euclidv1",
|
||||
Phase::EuclidV2 => "euclidv2",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn phase_spec_chunk(&self, workspace_path: &Path) -> ProverConfig {
|
||||
let dir_cache = Some(workspace_path.join("cache"));
|
||||
let path_app_exe = workspace_path.join("chunk/app.vmexe");
|
||||
let path_app_config = workspace_path.join("chunk/openvm.toml");
|
||||
let segment_len = Some((1 << 22) - 100);
|
||||
ProverConfig {
|
||||
dir_cache,
|
||||
path_app_config,
|
||||
path_app_exe,
|
||||
segment_len,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn phase_spec_batch(&self, workspace_path: &Path) -> ProverConfig {
|
||||
let dir_cache = Some(workspace_path.join("cache"));
|
||||
let path_app_exe = workspace_path.join("batch/app.vmexe");
|
||||
let path_app_config = workspace_path.join("batch/openvm.toml");
|
||||
let segment_len = Some((1 << 22) - 100);
|
||||
ProverConfig {
|
||||
dir_cache,
|
||||
path_app_config,
|
||||
path_app_exe,
|
||||
segment_len,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn phase_spec_bundle(&self, workspace_path: &Path) -> ProverConfig {
|
||||
let dir_cache = Some(workspace_path.join("cache"));
|
||||
let path_app_config = workspace_path.join("bundle/openvm.toml");
|
||||
let segment_len = Some((1 << 22) - 100);
|
||||
match self {
|
||||
Phase::EuclidV1 => ProverConfig {
|
||||
dir_cache,
|
||||
path_app_config,
|
||||
segment_len,
|
||||
path_app_exe: workspace_path.join("bundle/app_euclidv1.vmexe"),
|
||||
..Default::default()
|
||||
},
|
||||
Phase::EuclidV2 => ProverConfig {
|
||||
dir_cache,
|
||||
path_app_config,
|
||||
segment_len,
|
||||
path_app_exe: workspace_path.join("bundle/app.vmexe"),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for EuclidHandler {}
|
||||
|
||||
impl EuclidHandler {
|
||||
pub fn new(workspace_path: &str) -> Self {
|
||||
let p = Phase::EuclidV1;
|
||||
let workspace_path = Path::new(workspace_path);
|
||||
let chunk_prover = ChunkProver::setup(p.phase_spec_chunk(workspace_path))
|
||||
.expect("Failed to setup chunk prover");
|
||||
|
||||
let batch_prover = BatchProver::setup(p.phase_spec_batch(workspace_path))
|
||||
.expect("Failed to setup batch prover");
|
||||
|
||||
let bundle_prover = BundleProverEuclidV1::setup(p.phase_spec_bundle(workspace_path))
|
||||
.expect("Failed to setup bundle prover");
|
||||
|
||||
Self {
|
||||
chunk_prover,
|
||||
batch_prover,
|
||||
bundle_prover,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CircuitsHandler for Arc<Mutex<EuclidHandler>> {
|
||||
async fn get_vk(&self, task_type: ProofType) -> Option<Vec<u8>> {
|
||||
Some(match task_type {
|
||||
ProofType::Chunk => self.try_lock().unwrap().chunk_prover.get_app_vk(),
|
||||
ProofType::Batch => self.try_lock().unwrap().batch_prover.get_app_vk(),
|
||||
ProofType::Bundle => self.try_lock().unwrap().bundle_prover.get_app_vk(),
|
||||
_ => unreachable!("Unsupported proof type"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_proof_data(&self, prove_request: ProveRequest) -> Result<String> {
|
||||
match prove_request.proof_type {
|
||||
ProofType::Chunk => {
|
||||
let task: ChunkProvingTask = serde_json::from_str(&prove_request.input)?;
|
||||
let proof = self.try_lock().unwrap().chunk_prover.gen_proof(&task)?;
|
||||
|
||||
Ok(serde_json::to_string(&proof)?)
|
||||
}
|
||||
ProofType::Batch => {
|
||||
let task: BatchProvingTask = serde_json::from_str(&prove_request.input)?;
|
||||
let proof = self.try_lock().unwrap().batch_prover.gen_proof(&task)?;
|
||||
|
||||
Ok(serde_json::to_string(&proof)?)
|
||||
}
|
||||
ProofType::Bundle => {
|
||||
let batch_proofs: BundleProvingTask = serde_json::from_str(&prove_request.input)?;
|
||||
let proof = self
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.bundle_prover
|
||||
.gen_proof_evm(&batch_proofs)?;
|
||||
|
||||
Ok(serde_json::to_string(&proof)?)
|
||||
}
|
||||
_ => Err(anyhow!("Unsupported proof type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::Path,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use super::{CircuitsHandler, Phase};
|
||||
use crate::prover::CircuitConfig;
|
||||
use async_trait::async_trait;
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
use eyre::Result;
|
||||
use scroll_proving_sdk::prover::{proving_service::ProveRequest, ProofType};
|
||||
use scroll_zkvm_prover_euclid::{BatchProver, BundleProverEuclidV2, ChunkProver};
|
||||
use scroll_zkvm_types::ProvingTask;
|
||||
use tokio::sync::Mutex;
|
||||
pub struct EuclidV2Handler {
|
||||
chunk_prover: ChunkProver,
|
||||
batch_prover: BatchProver,
|
||||
bundle_prover: BundleProverEuclidV2,
|
||||
cached_vks: HashMap<ProofType, OnceLock<String>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for EuclidV2Handler {}
|
||||
|
||||
impl EuclidV2Handler {
|
||||
pub fn new(cfg: &CircuitConfig) -> Self {
|
||||
let workspace_path = &cfg.workspace_path;
|
||||
let p = Phase::EuclidV2;
|
||||
let workspace_path = Path::new(workspace_path);
|
||||
let chunk_prover = ChunkProver::setup(p.phase_spec_chunk(workspace_path))
|
||||
.expect("Failed to setup chunk prover");
|
||||
|
||||
let batch_prover = BatchProver::setup(p.phase_spec_batch(workspace_path))
|
||||
.expect("Failed to setup batch prover");
|
||||
|
||||
let bundle_prover = BundleProverEuclidV2::setup(p.phase_spec_bundle(workspace_path))
|
||||
.expect("Failed to setup bundle prover");
|
||||
|
||||
let build_vk_cache = |proof_type: ProofType| {
|
||||
let vk = if let Some(vk) = cfg.vks.get(&proof_type) {
|
||||
OnceLock::from(vk.clone())
|
||||
} else {
|
||||
OnceLock::new()
|
||||
};
|
||||
(proof_type, vk)
|
||||
};
|
||||
|
||||
Self {
|
||||
chunk_prover,
|
||||
batch_prover,
|
||||
bundle_prover,
|
||||
cached_vks: HashMap::from([
|
||||
build_vk_cache(ProofType::Chunk),
|
||||
build_vk_cache(ProofType::Batch),
|
||||
build_vk_cache(ProofType::Bundle),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
/// get_prover get the inner prover, later we would replace chunk/batch/bundle_prover with
|
||||
/// universal prover, before that, use bundle_prover as the represent one
|
||||
pub fn get_prover(&self) -> &BundleProverEuclidV2 {
|
||||
&self.bundle_prover
|
||||
}
|
||||
|
||||
pub fn get_vk_and_cache(&self, task_type: ProofType) -> String {
|
||||
match task_type {
|
||||
ProofType::Chunk => self.cached_vks[&ProofType::Chunk]
|
||||
.get_or_init(|| BASE64_STANDARD.encode(self.chunk_prover.get_app_vk())),
|
||||
ProofType::Batch => self.cached_vks[&ProofType::Batch]
|
||||
.get_or_init(|| BASE64_STANDARD.encode(self.batch_prover.get_app_vk())),
|
||||
ProofType::Bundle => self.cached_vks[&ProofType::Bundle]
|
||||
.get_or_init(|| BASE64_STANDARD.encode(self.bundle_prover.get_app_vk())),
|
||||
_ => unreachable!("Unsupported proof type {:?}", task_type),
|
||||
}
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CircuitsHandler for Arc<Mutex<EuclidV2Handler>> {
|
||||
async fn get_vk(&self, task_type: ProofType) -> String {
|
||||
self.lock().await.get_vk_and_cache(task_type)
|
||||
}
|
||||
|
||||
async fn get_proof_data(&self, prove_request: ProveRequest) -> Result<String> {
|
||||
let handler_self = self.lock().await;
|
||||
let u_task: ProvingTask = serde_json::from_str(&prove_request.input)?;
|
||||
let expected_vk = handler_self.get_vk_and_cache(prove_request.proof_type);
|
||||
if BASE64_STANDARD.encode(&u_task.vk) != expected_vk {
|
||||
eyre::bail!(
|
||||
"vk is not match!, prove type {:?}, expected {}, get {}",
|
||||
prove_request.proof_type,
|
||||
expected_vk,
|
||||
BASE64_STANDARD.encode(&u_task.vk),
|
||||
);
|
||||
}
|
||||
|
||||
let proof = match prove_request.proof_type {
|
||||
ProofType::Chunk => handler_self
|
||||
.chunk_prover
|
||||
.gen_proof_universal(&u_task, false)?,
|
||||
ProofType::Batch => handler_self
|
||||
.batch_prover
|
||||
.gen_proof_universal(&u_task, false)?,
|
||||
ProofType::Bundle => handler_self
|
||||
.bundle_prover
|
||||
.gen_proof_universal(&u_task, true)?,
|
||||
_ => {
|
||||
return Err(eyre::eyre!(
|
||||
"Unsupported proof type {:?}",
|
||||
prove_request.proof_type
|
||||
))
|
||||
}
|
||||
};
|
||||
//TODO: check expected PI
|
||||
Ok(serde_json::to_string(&proof)?)
|
||||
}
|
||||
}
|
||||
55
crates/prover-bin/src/zk_circuits_handler/universal.rs
Normal file
55
crates/prover-bin/src/zk_circuits_handler/universal.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::path::Path;
|
||||
|
||||
use super::CircuitsHandler;
|
||||
use async_trait::async_trait;
|
||||
use eyre::Result;
|
||||
use scroll_proving_sdk::prover::ProofType;
|
||||
use scroll_zkvm_prover::{Prover, ProverConfig};
|
||||
use scroll_zkvm_types::ProvingTask;
|
||||
use tokio::sync::Mutex;
|
||||
pub struct UniversalHandler {
|
||||
prover: Prover,
|
||||
}
|
||||
|
||||
/// Safe for current usage as `CircuitsHandler` trait (protected inside of Mutex and NEVER extract
|
||||
/// the instance out by `into_inner`)
|
||||
unsafe impl Send for UniversalHandler {}
|
||||
|
||||
impl UniversalHandler {
|
||||
pub fn new(workspace_path: impl AsRef<Path>, _proof_type: ProofType) -> Result<Self> {
|
||||
let path_app_exe = workspace_path.as_ref().join("app.vmexe");
|
||||
let path_app_config = workspace_path.as_ref().join("openvm.toml");
|
||||
let segment_len = Some((1 << 22) - 100);
|
||||
let config = ProverConfig {
|
||||
path_app_config,
|
||||
path_app_exe,
|
||||
segment_len,
|
||||
};
|
||||
|
||||
let prover = Prover::setup(config, None)?;
|
||||
Ok(Self { prover })
|
||||
}
|
||||
|
||||
/// get_prover get the inner prover, later we would replace chunk/batch/bundle_prover with
|
||||
/// universal prover, before that, use bundle_prover as the represent one
|
||||
pub fn get_prover(&mut self) -> &mut Prover {
|
||||
&mut self.prover
|
||||
}
|
||||
|
||||
pub fn get_task_from_input(input: &str) -> Result<ProvingTask> {
|
||||
Ok(serde_json::from_str(input)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CircuitsHandler for Mutex<UniversalHandler> {
|
||||
async fn get_proof_data(&self, u_task: &ProvingTask, need_snark: bool) -> Result<String> {
|
||||
let mut handler_self = self.lock().await;
|
||||
|
||||
let proof = handler_self
|
||||
.get_prover()
|
||||
.gen_proof_universal(u_task, need_snark)?;
|
||||
|
||||
Ok(serde_json::to_string(&proof)?)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/pressly/goose/v3 v3.16.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305151038-478940e79601
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
)
|
||||
@@ -34,11 +34,9 @@ require (
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
|
||||
@@ -121,8 +121,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305151038-478940e79601 h1:NEsjCG6uSvLRBlsP3+x6PL1kM+Ojs3g8UGotIPgJSz8=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305151038-478940e79601/go.mod h1:OblWe1+QrZwdpwO0j/LY3BSGuKT3YPUFBDQQgvvfStQ=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63 h1:xuqdhD4w/zcI5T8Ty1wHvqB75P2HNg3jTH/kUEHGt9Y=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63/go.mod h1:zRa7CnS75mFdgp8IeMtZV/wCAlxPRT33Ek3+fFbBJVQ=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec=
|
||||
|
||||
19
go.work.sum
19
go.work.sum
@@ -731,8 +731,9 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/compose-spec/compose-go v1.20.0 h1:h4ZKOst1EF/DwZp7dWkb+wbTVE4nEyT9Lc89to84Ol4=
|
||||
github.com/compose-spec/compose-go v1.20.0/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM=
|
||||
github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
|
||||
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
||||
github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
|
||||
github.com/consensys/gnark-crypto v0.13.0/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o=
|
||||
github.com/container-orchestrated-devices/container-device-interface v0.6.1 h1:mz77uJoP8im/4Zins+mPqt677ZMaflhoGaYrRAl5jvA=
|
||||
github.com/container-orchestrated-devices/container-device-interface v0.6.1/go.mod h1:40T6oW59rFrL/ksiSs7q45GzjGlbvxnA4xaK6cyq+kA=
|
||||
github.com/containerd/aufs v1.0.0 h1:2oeJiwX5HstO7shSrPZjrohJZLzK36wvpdmzDRkL/LY=
|
||||
@@ -1083,9 +1084,7 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/holiman/uint256 v1.3.0/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM=
|
||||
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150 h1:vlNjIqmUZ9CMAWsbURYl3a6wZbw7q5RHVvlXTNS/Bs8=
|
||||
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
|
||||
github.com/hydrogen18/memlistener v1.0.0/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
|
||||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
@@ -1122,7 +1121,6 @@ github.com/intel/goresctrl v0.3.0 h1:K2D3GOzihV7xSBedGxONSlaw/un1LZgWsc9IfqipN4c
|
||||
github.com/intel/goresctrl v0.3.0/go.mod h1:fdz3mD85cmP9sHD8JUlrNWAxvwM86CrbmVXltEKd7zk=
|
||||
github.com/iris-contrib/jade v1.1.4/go.mod h1:EDqR+ur9piDl6DUgs6qRrlfzmlx/D5UybogqrXvJTBE=
|
||||
github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U=
|
||||
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
|
||||
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E=
|
||||
@@ -1202,6 +1200,7 @@ github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
|
||||
@@ -1228,7 +1227,6 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
@@ -1240,7 +1238,6 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
@@ -1413,11 +1410,15 @@ github.com/scroll-tech/da-codec v0.1.3-0.20250609113414-f33adf0904bd h1:NUol+dPt
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250609113414-f33adf0904bd/go.mod h1:gz5x3CsLy5htNTbv4PWRPBU9nSAujfx1U2XtFcXoFuk=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250609154559-8935de62c148 h1:cyK1ifU2fRoMl8YWR9LOsZK4RvJnlG3RODgakj5I8VY=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250609154559-8935de62c148/go.mod h1:gz5x3CsLy5htNTbv4PWRPBU9nSAujfx1U2XtFcXoFuk=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250825071838-cddc263e5ef6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20240607130425-e2becce6a1a4/go.mod h1:byf/mZ8jLYUCnUePTicjJWn+RvKdxDn7buS6glTnMwQ=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20240821074444-b3fa00861e5e/go.mod h1:swB5NSp8pKNDuYsTxfR08bHS6L56i119PBx8fxvV8Cs=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20241010064814-3d88e870ae22/go.mod h1:r9FwtxCtybMkTbWYCyBuevT9TW3zHmOTHqD082Uh+Oo=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250206083728-ea43834c198f/go.mod h1:Ik3OBLl7cJxPC+CFyCBYNXBPek4wpdzkWehn/y5qLM8=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250225152658-bcfdb48dd939/go.mod h1:AgU8JJxC7+nfs7R7ma35AU7dMAGW7wCw3dRZRefIKyQ=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017081611-2bc7a5482dcc h1:zSO+VMyzmEVezVuMC7jZ9PcvihwmrlKt+7cyv9rpq2s=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017081611-2bc7a5482dcc/go.mod h1:zRa7CnS75mFdgp8IeMtZV/wCAlxPRT33Ek3+fFbBJVQ=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
@@ -1454,7 +1455,6 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.1 h1:RT9kM8MZLZIsPTH+HKQEP5yaAk3yd/VBzlINaRjXs8k=
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.1/go.mod h1:5qg6rpqlwIub0JAiF1UK9IMD6BpPTmvG6yfSgDBs5lg=
|
||||
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
|
||||
@@ -1481,7 +1481,6 @@ github.com/tonistiigi/go-archvariant v1.0.0 h1:5LC1eDWiBNflnTF1prCiX09yfNHIxDC/a
|
||||
github.com/tonistiigi/go-archvariant v1.0.0/go.mod h1:TxFmO5VS6vMq2kvs3ht04iPXtu2rUT/erOnGFYfk5Ho=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
|
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||
@@ -1716,7 +1715,6 @@ golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/perf v0.0.0-20230113213139-801c7ef9e5c5/go.mod h1:UBKtEnL8aqnd+0JHqZ+2qoMDwtuy6cYhhKNoHLBiTQc=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1741,11 +1739,9 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1811,7 +1807,6 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
22
permissionless-batches/Makefile
Normal file
22
permissionless-batches/Makefile
Normal file
@@ -0,0 +1,22 @@
|
||||
.PHONY: batch_production_submission launch_prover psql check_proving_status
|
||||
|
||||
export SCROLL_ZKVM_VERSION=0.4.2
|
||||
PG_URL=postgres://postgres@localhost:5432/scroll
|
||||
|
||||
batch_production_submission:
|
||||
docker compose --profile batch-production-submission up
|
||||
|
||||
launch_prover:
|
||||
docker compose up -d
|
||||
|
||||
psql:
|
||||
psql 'postgres://postgres@localhost:5432/scroll'
|
||||
|
||||
check_proving_status:
|
||||
@echo "Checking proving status..."
|
||||
@result=$$(psql "${PG_URL}" -t -c "SELECT proving_status = 4 AS is_status_success FROM batch ORDER BY index LIMIT 1;" | tr -d '[:space:]'); \
|
||||
if [ "$$result" = "t" ]; then \
|
||||
echo "✅ Prove succeeded! You're ready to submit permissionless batch and proof!"; \
|
||||
else \
|
||||
echo "Proof is not ready..."; \
|
||||
fi
|
||||
172
permissionless-batches/README.md
Normal file
172
permissionless-batches/README.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Permissionless Batches
|
||||
Permissionless batches aka enforced batches is a feature that provides guarantee to users that they can exit Scroll even if the operator is down or censoring.
|
||||
It allows anyone to take over and submit a batch (permissionless batch submission) together with a proof after a certain time period has passed without a batch being finalized on L1.
|
||||
|
||||
Once permissionless batch mode is activated, the operator can no longer submit batches in a permissioned way. Only the security council can deactivate permissionless batch mode and reinstate the operator as the only batch submitter.
|
||||
There are two types of situations to consider:
|
||||
- `Permissionless batch mode is activated:` This means that finalization halted for some time. Now anyone can submit batches utilizing the [batch production toolkit](#batch-production-toolkit).
|
||||
- `Permissionless batch mode is deactivated:` This means that the security council has decided to reinstate the operator as the only batch submitter. The operator needs to [recover](#operator-recovery) the sequencer and relayer to resume batch submission and the valid L2 chain.
|
||||
|
||||
|
||||
## Batch production toolkit
|
||||
The batch production toolkit is a set of tools that allow anyone to submit a batch in permissionless mode. It consists of three main components:
|
||||
1. l2geth state recovery from L1
|
||||
2. l2geth block production
|
||||
3. production, proving and submission of batch with `docker-compose.yml`
|
||||
|
||||
### Prerequisites
|
||||
- Unix-like OS, 32GB RAM
|
||||
- Docker
|
||||
- [l2geth](https://github.com/scroll-tech/go-ethereum/) or [Docker image](https://hub.docker.com/r/scrolltech/l2geth) of corresponding [version](https://docs.scroll.io/en/technology/overview/scroll-upgrades/).
|
||||
- access to an Ethereum L1 RPC node (beacon node and execution client)
|
||||
- ability to run a prover
|
||||
- L1 account with funds to pay for the batch submission
|
||||
|
||||
### 1. l2geth state recovery from L1
|
||||
Once permissionless mode is activated there's no blocks being produced and propagated on L2. The first step is to recover the latest state of the L2 chain from L1. This is done by running l2geth in recovery mode.
|
||||
|
||||
Running l2geth in recovery mode requires following configuration:
|
||||
- `--scroll` or `--scroll-sepolia` - enables Scroll Mainnet or Sepolia mode
|
||||
- `--da.blob.beaconnode` - L1 RPC beacon node
|
||||
- `--l1.endpoint` - L1 RPC execution client
|
||||
- `--da.sync=true` - enables syncing with L1
|
||||
- `--da.recovery` - enables recovery mode
|
||||
- `--da.recovery.initiall1block` - initial L1 block (commit tx of initial batch)
|
||||
- `--da.recovery.initialbatch` - batch where to start recovery from. Can be found on [Scrollscan Explorer](https://scrollscan.com/batches).
|
||||
- `--da.recovery.l2endblock` - until which L2 block recovery should run (optional)
|
||||
|
||||
```bash
|
||||
./build/bin/geth --scroll<-sepolia> \
|
||||
--datadir "tmp/datadir" \
|
||||
--gcmode archive \
|
||||
--http --http.addr "0.0.0.0" --http.port 8545 --http.api "eth,net,web3,debug,scroll" --http.vhosts "*" \
|
||||
--da.blob.beaconnode "<L1 RPC beacon node>" \
|
||||
--l1.endpoint "<L1 RPC execution client>" \
|
||||
--da.sync=true --da.recovery --da.recovery.initiall1block "<initial L1 block (commit tx of initial batch)>" --da.recovery.initialbatch "<batch where to start recovery from>" --da.recovery.l2endblock "<until which L2 block recovery should run (optional)>" \
|
||||
--verbosity 3
|
||||
```
|
||||
|
||||
### 2. l2geth block production
|
||||
After the state is recovered, the next step is to produce blocks on L2. This is done by running l2geth in block production mode.
|
||||
As a prerequisite, the state recovery must be completed and the latest state of the L2 chain must be available.
|
||||
|
||||
You also need to generate a keystore e.g. with [Clef](https://geth.ethereum.org/docs/fundamentals/account-management) to be able to sign blocks.
|
||||
This key is not used for any funds, but required for block production to work. Once you generated blocks you can safely discard it.
|
||||
|
||||
Running l2geth in block production mode requires following configuration:
|
||||
- `--scroll` or `--scroll-sepolia` - enables Scroll Mainnet or Sepolia mode
|
||||
- `--da.blob.beaconnode` - L1 RPC beacon node
|
||||
- `--l1.endpoint` - L1 RPC execution client
|
||||
- `--da.sync=true` - enables syncing with L1
|
||||
- `--da.recovery` - enables recovery mode
|
||||
- `--da.recovery.produceblocks` - enables block production
|
||||
- `--miner.etherbase '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' --mine` - enables mining. the address is not used, but required for mining to work
|
||||
- `---miner.gaslimit 1 --miner.gasprice 1 --miner.maxaccountsnum 100 --rpc.gascap 0 --gpo.ignoreprice 1` - gas limits for block production
|
||||
|
||||
```bash
|
||||
./build/bin/geth --scroll<-sepolia> \
|
||||
--datadir "tmp/datadir" \
|
||||
--gcmode archive \
|
||||
--http --http.addr "0.0.0.0" --http.port 8545 --http.api "eth,net,web3,debug,scroll" --http.vhosts "*" \
|
||||
--da.blob.beaconnode "<L1 RPC beacon node>" \
|
||||
--l1.endpoint "<L1 RPC execution client>" \
|
||||
--da.sync=true --da.recovery --da.recovery.produceblocks \
|
||||
--miner.gaslimit 1 --miner.gasprice 1 --miner.maxaccountsnum 100 --rpc.gascap 0 --gpo.ignoreprice 1 \
|
||||
--miner.etherbase '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' --mine \
|
||||
--verbosity 3
|
||||
```
|
||||
|
||||
### 3. production, proving and submission of batch with `docker-compose.yml`
|
||||
After the blocks are produced, the next step is to produce a batch, prove it and submit it to L1. This is done by running the `docker-compose.yml` in the `permissionless-batches` folder.
|
||||
|
||||
|
||||
#### Producing a batch
|
||||
To produce a batch you need to run the `batch-production-submission` profile in `docker-compose.yml`.
|
||||
|
||||
1. Fill `conf/genesis.json` with the latest genesis state from the L2 chain. The genesis for the current fork can be found [here](https://docs.scroll.io/en/technology/overview/scroll-upgrades/).
|
||||
2. Make sure that `l2geth` with your locally produced blocks is running and reachable from the Docker network (e.g. `http://host.docker.internal:8545`)
|
||||
3. Fill in required fields in `conf/relayer/config.json`
|
||||
|
||||
|
||||
Run with `make batch_production_submission`.
|
||||
This will produce chunks, a batch and bundle which will be proven in the next step.
|
||||
`Success! You're ready to generate proofs!` indicates that everything is working correctly and the batch is ready to be proven.
|
||||
|
||||
#### Proving a batch
|
||||
To prove the chunk, batch and bundle you just generated you need to run the `prover` profile in `docker-compose.yml`.
|
||||
|
||||
Local Proving:
|
||||
|
||||
1. Hardware spec for local prover: CPU: 36+ core, 128G memory GPU: 24G memory (e.g. Rtx 3090/3090Ti/4090/A10/L4)
|
||||
2. Make sure `verifier` and `high_version_circuit` in `conf/coordinator/config.json` are correct for the [latest fork](https://docs.scroll.io/en/technology/overview/scroll-upgrades/)
|
||||
3. Set the `SCROLL_ZKVM_VERSION` environment variable on `Makefile` to the correct [version](https://docs.scroll.io/en/technology/overview/scroll-upgrades/).
|
||||
4. Fill in the required fields in `conf/proving-service/config.json`
|
||||
|
||||
Run with `make launch_prover`.
|
||||
|
||||
|
||||
#### Batch submission
|
||||
To submit the batch you need to run the `batch-production-submission` profile in `docker-compose.yml`.
|
||||
|
||||
1. Fill in required fields in `conf/relayer/config.json` for the sender config.
|
||||
|
||||
Run with `make batch_production_submission`.
|
||||
This will submit the batch to L1 and finalize it. The transaction will be retried in case of failure.
|
||||
|
||||
**Troubleshooting**
|
||||
- in case the submission fails it will print the calldata for the transaction in an error message. You can use this with `cast call --trace --rpc-url "$SCROLL_L1_DEPLOYMENT_RPC" "$L1_SCROLL_CHAIN_PROXY_ADDR" <calldata>` to see what went wrong.
|
||||
- `0x4df567b9: ErrorNotInEnforcedBatchMode`: permissionless batch mode is not activated, you can't submit a batch
|
||||
- `0xa5d305cc: ErrorBatchIsEmpty`: no blob was provided. This is usually returned if you do the `cast call`, permissionless mode is activated but you didn't provide a blob in the transaction.
|
||||
|
||||
## Operator recovery
|
||||
Operator recovery needs to be run by the rollup operator to resume normal rollup operation after permissionless batch mode is deactivated. It consists of two main components:
|
||||
1. l2geth recovery
|
||||
2. Relayer recovery
|
||||
|
||||
These steps are required to resume permissioned batch submission and the valid L2 chain. They will restore the entire history of the batches submitted during permissionless mode.
|
||||
|
||||
### Prerequisites
|
||||
- l2geth with the latest state of the L2 chain (before permissionless mode was activated)
|
||||
- signer key for the sequencer according to Clique consensus
|
||||
- relayer and coordinator are set up, running and up-to-date with the latest state of the L2 chain (before permissionless mode was activated)
|
||||
|
||||
### l2geth recovery
|
||||
Running l2geth in recovery mode requires following configuration:
|
||||
- `--scroll` or `--scroll-sepolia` - enables Scroll Mainnet or Sepolia mode
|
||||
- `--da.blob.beaconnode` - L1 RPC beacon node
|
||||
- `--l1.endpoint` - L1 RPC execution client
|
||||
- `--da.sync=true` - enables syncing with L1
|
||||
- `--da.recovery` - enables recovery mode
|
||||
- `--da.recovery.signblocks` - enables signing blocks with the sequencer and configured key
|
||||
- `--da.recovery.initiall1block` - initial L1 block (commit tx of initial batch)
|
||||
- `--da.recovery.initialbatch` - batch where to start recovery from. Can be found on [Scrollscan Explorer](https://scrollscan.com/batches).
|
||||
- `--da.recovery.l2endblock` - until which L2 block recovery should run (optional)
|
||||
|
||||
```bash
|
||||
./build/bin/geth --scroll<-sepolia> \
|
||||
--datadir "tmp/datadir" \
|
||||
--gcmode archive \
|
||||
--http --http.addr "0.0.0.0" --http.port 8545 --http.api "eth,net,web3,debug,scroll" --http.vhosts "*" \
|
||||
--da.blob.beaconnode "<L1 RPC beacon node>" \
|
||||
--l1.endpoint "<L1 RPC execution client>" \
|
||||
--da.sync=true --da.recovery --da.recovery.signblocks --da.recovery.initiall1block "<initial L1 block (commit tx of initial batch)>" --da.recovery.initialbatch "<batch where to start recovery from>" --da.recovery.l2endblock "<until which L2 block recovery should run (optional)>" \
|
||||
--verbosity 3
|
||||
```
|
||||
|
||||
After the recovery is finished, start the sequencer in normal operation and continue issuing L2 blocks as normal. This will resume the L2 chain, allow the relayer (after running recovery) to create new batches and allow other L2 follower nodes to sync up the valid and signed L2 chain.
|
||||
|
||||
### Relayer recovery
|
||||
Start the relayer with the following additional top-level configuration:
|
||||
```
|
||||
"recovery_config": {
|
||||
"enable": true
|
||||
}
|
||||
```
|
||||
|
||||
This will make the relayer recover all the chunks, batches and bundles that were submitted during permissionless mode. These batches are marked automatically as proven and finalized.
|
||||
Once this process is finished, start the relayer normally without the recovery config to resume normal operation.
|
||||
```
|
||||
"recovery_config": {
|
||||
"enable": false
|
||||
}
|
||||
```
|
||||
30
permissionless-batches/conf/coordinator/config.json
Normal file
30
permissionless-batches/conf/coordinator/config.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"prover_manager": {
|
||||
"provers_per_session": 1,
|
||||
"session_attempts": 100,
|
||||
"chunk_collection_time_sec": 36000,
|
||||
"batch_collection_time_sec": 2700,
|
||||
"bundle_collection_time_sec": 2700,
|
||||
"verifier": {
|
||||
"high_version_circuit" : {
|
||||
"fork_name": "euclid",
|
||||
"assets_path": "/verifier/openvm/verifier",
|
||||
"min_prover_version": "v4.5.7"
|
||||
}
|
||||
}
|
||||
},
|
||||
"db": {
|
||||
"driver_name": "postgres",
|
||||
"dsn": "postgres://db/scroll?sslmode=disable&user=postgres",
|
||||
"maxOpenNum": 200,
|
||||
"maxIdleNum": 20
|
||||
},
|
||||
"l2": {
|
||||
"chain_id": 333333
|
||||
},
|
||||
"auth": {
|
||||
"secret": "e788b62d39254928a821ac1c76b274a8c835aa1e20ecfb6f50eb10e87847de44",
|
||||
"challenge_expire_duration_sec": 10,
|
||||
"login_expire_duration_sec": 3600
|
||||
}
|
||||
}
|
||||
76
permissionless-batches/conf/coordinator/coordinator_run.sh
Executable file
76
permissionless-batches/conf/coordinator/coordinator_run.sh
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
apt update
|
||||
apt install -y wget libdigest-sha-perl
|
||||
|
||||
# release version
|
||||
if [ -z "${SCROLL_ZKVM_VERSION}" ]; then
|
||||
echo "SCROLL_ZKVM_VERSION not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${HTTP_PORT}" ]; then
|
||||
echo "HTTP_PORT not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${METRICS_PORT}" ]; then
|
||||
echo "METRICS_PORT not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case $CHAIN_ID in
|
||||
"5343532222") # staging network
|
||||
echo "staging network not supported"
|
||||
exit 1
|
||||
;;
|
||||
"534353") # alpha network
|
||||
echo "alpha network not supported"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
BASE_DOWNLOAD_DIR="/verifier"
|
||||
# Ensure the base directory exists
|
||||
mkdir -p "$BASE_DOWNLOAD_DIR"
|
||||
|
||||
# Set subdirectories
|
||||
OPENVM_DIR="$BASE_DOWNLOAD_DIR/openvm"
|
||||
|
||||
# Create necessary directories
|
||||
mkdir -p "$OPENVM_DIR/verifier"
|
||||
|
||||
# Define URLs for OpenVM files (No checksum verification)
|
||||
OPENVM_URLS=(
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/verifier/verifier.bin"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/verifier/root-verifier-vm-config"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/verifier/root-verifier-committed-exe"
|
||||
)
|
||||
|
||||
# Download OpenVM files (No checksum verification, but skips if file exists)
|
||||
for url in "${OPENVM_URLS[@]}"; do
|
||||
dest_subdir="$OPENVM_DIR/$(basename $(dirname "$url"))"
|
||||
mkdir -p "$dest_subdir"
|
||||
|
||||
filepath="$dest_subdir/$(basename "$url")"
|
||||
echo "Downloading $filepath..."
|
||||
curl -o "$filepath" -L "$url"
|
||||
done
|
||||
|
||||
mkdir -p "$HOME/.openvm"
|
||||
ln -s "$OPENVM_DIR/params" "$HOME/.openvm/params"
|
||||
|
||||
echo "All files downloaded successfully! 🎉"
|
||||
|
||||
mkdir -p /usr/local/bin
|
||||
wget https://github.com/ethereum/solidity/releases/download/v0.8.19/solc-static-linux -O /usr/local/bin/solc
|
||||
chmod +x /usr/local/bin/solc
|
||||
|
||||
# Start coordinator
|
||||
echo "Starting coordinator api"
|
||||
|
||||
RUST_BACKTRACE=1 exec coordinator_api --config /coordinator/config.json \
|
||||
--genesis /coordinator/genesis.json \
|
||||
--http --http.addr "0.0.0.0" --http.port ${HTTP_PORT} \
|
||||
--metrics --metrics.addr "0.0.0.0" --metrics.port ${METRICS_PORT} \
|
||||
--log.debug
|
||||
1
permissionless-batches/conf/genesis.json
Normal file
1
permissionless-batches/conf/genesis.json
Normal file
@@ -0,0 +1 @@
|
||||
<fill with correct genesis.json>
|
||||
28
permissionless-batches/conf/proving-service/config.json
Normal file
28
permissionless-batches/conf/proving-service/config.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"sdk_config": {
|
||||
"prover_name_prefix": "local_prover",
|
||||
"keys_dir": "/keys",
|
||||
"db_path": "/db",
|
||||
"coordinator": {
|
||||
"base_url": "http://172.17.0.1:8556",
|
||||
"retry_count": 10,
|
||||
"retry_wait_time_sec": 10,
|
||||
"connection_timeout_sec": 30
|
||||
},
|
||||
"l2geth": {
|
||||
"endpoint": "<L2 RPC with generated blocks reachable from Docker network>"
|
||||
},
|
||||
"prover": {
|
||||
"circuit_type": 2,
|
||||
"supported_proof_types": [1,2,3],
|
||||
"circuit_version": "v0.13.1"
|
||||
},
|
||||
"health_listener_addr": "0.0.0.0:89"
|
||||
},
|
||||
"circuits": {
|
||||
"euclidV2": {
|
||||
"hard_fork_name": "euclidV2",
|
||||
"workspace_path": "/openvm"
|
||||
}
|
||||
}
|
||||
}
|
||||
54
permissionless-batches/conf/proving-service/prover_run.sh
Normal file
54
permissionless-batches/conf/proving-service/prover_run.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
apt update
|
||||
apt install -y wget curl
|
||||
|
||||
# release version
|
||||
if [ -z "${SCROLL_ZKVM_VERSION}" ]; then
|
||||
echo "SCROLL_ZKVM_VERSION not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_DOWNLOAD_DIR="/openvm"
|
||||
# Ensure the base directory exists
|
||||
mkdir -p "$BASE_DOWNLOAD_DIR"
|
||||
|
||||
# Define URLs for OpenVM files (No checksum verification)
|
||||
OPENVM_URLS=(
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/chunk/app.vmexe"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/chunk/openvm.toml"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/batch/app.vmexe"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/batch/openvm.toml"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/app.vmexe"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/app_euclidv1.vmexe"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/openvm.toml"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/verifier.bin"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/verifier.sol"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/digest_1.hex"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/digest_2.hex"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/digest_1_euclidv1.hex"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/$SCROLL_ZKVM_VERSION/bundle/digest_2_euclidv1.hex"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/params/kzg_bn254_22.srs"
|
||||
"https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/params/kzg_bn254_24.srs"
|
||||
)
|
||||
|
||||
# Download OpenVM files (No checksum verification, but skips if file exists)
|
||||
for url in "${OPENVM_URLS[@]}"; do
|
||||
dest_subdir="$BASE_DOWNLOAD_DIR/$(basename $(dirname "$url"))"
|
||||
mkdir -p "$dest_subdir"
|
||||
|
||||
filepath="$dest_subdir/$(basename "$url")"
|
||||
echo "Downloading $filepath..."
|
||||
curl -o "$filepath" -L "$url"
|
||||
done
|
||||
|
||||
mkdir -p "$HOME/.openvm"
|
||||
ln -s "/openvm/params" "$HOME/.openvm/params"
|
||||
|
||||
mkdir -p /usr/local/bin
|
||||
wget https://github.com/ethereum/solidity/releases/download/v0.8.19/solc-static-linux -O /usr/local/bin/solc
|
||||
chmod +x /usr/local/bin/solc
|
||||
|
||||
mkdir -p /openvm/cache
|
||||
|
||||
RUST_MIN_STACK=16777216 RUST_BACKTRACE=1 exec /prover/prover --config /prover/conf/config.json
|
||||
48
permissionless-batches/conf/relayer/config.json
Normal file
48
permissionless-batches/conf/relayer/config.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"l1_config": {
|
||||
"endpoint": "<L1 RPC execution node>"
|
||||
},
|
||||
"l2_config": {
|
||||
"confirmations": "0x0",
|
||||
"endpoint": "<L2 RPC with generated blocks reachable from Docker network>",
|
||||
"relayer_config": {
|
||||
"commit_sender_signer_config": {
|
||||
"signer_type": "PrivateKey",
|
||||
"private_key_signer_config": {
|
||||
"private_key": "<the private key of L1 address to submit permissionless batch, please fund it in advance>"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chunk_proposer_config": {
|
||||
"propose_interval_milliseconds": 100,
|
||||
"max_l2_gas_per_chunk": 20000000,
|
||||
"chunk_timeout_sec": 300,
|
||||
"max_uncompressed_batch_bytes_size": 4194304
|
||||
},
|
||||
"batch_proposer_config": {
|
||||
"propose_interval_milliseconds": 1000,
|
||||
"batch_timeout_sec": 300,
|
||||
"max_chunks_per_batch": 45,
|
||||
"max_uncompressed_batch_bytes_size": 4194304
|
||||
},
|
||||
"bundle_proposer_config": {
|
||||
"max_batch_num_per_bundle": 20,
|
||||
"bundle_timeout_sec": 36000
|
||||
}
|
||||
},
|
||||
"db_config": {
|
||||
"driver_name": "postgres",
|
||||
"dsn": "postgres://172.17.0.1:5432/scroll?sslmode=disable&user=postgres",
|
||||
"maxOpenNum": 200,
|
||||
"maxIdleNum": 20
|
||||
},
|
||||
"recovery_config": {
|
||||
"enable": true,
|
||||
"l1_block_height": "<commit tx of last finalized batch on L1>",
|
||||
"latest_finalized_batch": "<last finalized batch on L1>",
|
||||
"l2_block_height_limit": "<L2 block up to which to produce batch>",
|
||||
"force_latest_finalized_batch": false,
|
||||
"force_l1_message_count": 0,
|
||||
"submit_without_proof": false
|
||||
}
|
||||
}
|
||||
98
permissionless-batches/docker-compose.yml
Normal file
98
permissionless-batches/docker-compose.yml
Normal file
@@ -0,0 +1,98 @@
|
||||
name: permissionless-batches
|
||||
|
||||
services:
|
||||
relayer-batch-production:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: build/dockerfiles/recovery_permissionless_batches.Dockerfile
|
||||
network_mode: host
|
||||
container_name: permissionless-batches-relayer
|
||||
volumes:
|
||||
- ./conf/relayer/config.json:/app/conf/config.json
|
||||
- ./conf/genesis.json:/app/conf/genesis.json
|
||||
command: "--config /app/conf/config.json --min-codec-version 0"
|
||||
profiles:
|
||||
- batch-production-submission
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: postgres:17.0
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: scroll
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
||||
interval: 1s
|
||||
timeout: 1s
|
||||
retries: 10
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
coordinator-api:
|
||||
image: scrolltech/coordinator-api:v4.5.19
|
||||
volumes:
|
||||
- ./conf/coordinator/config.json:/coordinator/config.json:ro
|
||||
- ./conf/genesis.json:/coordinator/genesis.json:ro
|
||||
- ./conf/coordinator/coordinator_run.sh:/bin/coordinator_run.sh
|
||||
entrypoint: /bin/coordinator_run.sh
|
||||
profiles:
|
||||
- local-prover
|
||||
- cloud-prover
|
||||
ports: [8556:8555]
|
||||
environment:
|
||||
- SCROLL_ZKVM_VERSION=${SCROLL_ZKVM_VERSION}
|
||||
- SCROLL_PROVER_ASSETS_DIR=/verifier/assets/
|
||||
- HTTP_PORT=8555
|
||||
- METRICS_PORT=8390
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8555/coordinator/v1/challenge"]
|
||||
interval: 1s
|
||||
timeout: 1s
|
||||
retries: 10
|
||||
start_period: 5m
|
||||
|
||||
coordinator-cron:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: build/dockerfiles/coordinator-cron.Dockerfile
|
||||
volumes:
|
||||
- ./conf/coordinator/config.json:/app/conf/config.json
|
||||
command: "--config /app/conf/config.json --verbosity 3"
|
||||
profiles:
|
||||
- local-prover
|
||||
- cloud-prover
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
local-prover:
|
||||
image: scrolltech/cuda-prover:v4.5.12-97de9882-6ad5d8c-261b322
|
||||
network_mode: host
|
||||
platform: linux/amd64
|
||||
runtime: nvidia
|
||||
entrypoint: /bin/prover_run.sh
|
||||
environment:
|
||||
- SCROLL_ZKVM_VERSION=${SCROLL_ZKVM_VERSION}
|
||||
- LD_LIBRARY_PATH=/prover:/usr/local/cuda/lib64
|
||||
- RUST_MIN_STACK=16777216
|
||||
- RUST_BACKTRACE=1
|
||||
- RUST_LOG=info
|
||||
volumes:
|
||||
- ./conf/proving-service/config.json:/prover/conf/config.json:ro
|
||||
- ./conf/proving-service/prover_run.sh:/bin/prover_run.sh
|
||||
- ./conf/proving-service/db:/db
|
||||
- ./conf/proving-service/keys:/keys
|
||||
depends_on:
|
||||
coordinator-api:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
@@ -1,13 +1,12 @@
|
||||
package bridgeabi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestPackCommitBatches(t *testing.T) {
|
||||
@@ -92,20 +91,3 @@ func TestPackSetL2BaseFee(t *testing.T) {
|
||||
_, err = l2GasOracleABI.Pack("setL2BaseFee", baseFee)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestPrintABISignatures(t *testing.T) {
|
||||
// print all error signatures of ABI
|
||||
abi, err := ScrollChainMetaData.GetAbi()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, method := range abi.Methods {
|
||||
fmt.Println(hexutil.Encode(method.ID[:4]), method.Sig, method.Name)
|
||||
}
|
||||
|
||||
fmt.Println("------------------------------")
|
||||
for _, errors := range abi.Errors {
|
||||
fmt.Println(hexutil.Encode(errors.ID[:4]), errors.Sig, errors.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func action(ctx *cli.Context) error {
|
||||
log.Crit("failed to create l2 relayer", "config file", cfgFile, "error", err)
|
||||
}
|
||||
|
||||
go utils.Loop(subCtx, 2*time.Second, blobUploader.UploadBlobToS3)
|
||||
go utils.Loop(subCtx, 1*time.Second, blobUploader.UploadBlobToS3)
|
||||
|
||||
// Finish start all blob-uploader functions.
|
||||
log.Info("Start blob-uploader successfully", "version", version.Version)
|
||||
|
||||
144
rollup/cmd/permissionless_batches/app/app.go
Normal file
144
rollup/cmd/permissionless_batches/app/app.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
|
||||
"scroll-tech/common/database"
|
||||
"scroll-tech/common/observability"
|
||||
"scroll-tech/common/utils"
|
||||
"scroll-tech/common/version"
|
||||
|
||||
"scroll-tech/rollup/internal/config"
|
||||
"scroll-tech/rollup/internal/controller/permissionless_batches"
|
||||
"scroll-tech/rollup/internal/controller/watcher"
|
||||
)
|
||||
|
||||
var app *cli.App
|
||||
|
||||
func init() {
|
||||
// Set up rollup-relayer app info.
|
||||
app = cli.NewApp()
|
||||
app.Action = action
|
||||
app.Name = "permissionless-batches"
|
||||
app.Usage = "The Scroll Rollup Relayer for permissionless batch production"
|
||||
app.Version = version.Version
|
||||
app.Flags = append(app.Flags, utils.CommonFlags...)
|
||||
app.Flags = append(app.Flags, utils.RollupRelayerFlags...)
|
||||
app.Commands = []*cli.Command{}
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
return utils.LogSetup(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func action(ctx *cli.Context) error {
|
||||
// Load config file.
|
||||
cfgFile := ctx.String(utils.ConfigFileFlag.Name)
|
||||
cfg, err := config.NewConfig(cfgFile)
|
||||
if err != nil {
|
||||
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
|
||||
}
|
||||
|
||||
subCtx, cancel := context.WithCancel(ctx.Context)
|
||||
defer cancel()
|
||||
|
||||
// Sanity check config. Make sure the required fields are set.
|
||||
if cfg.RecoveryConfig == nil {
|
||||
return fmt.Errorf("recovery config must be specified")
|
||||
}
|
||||
if cfg.RecoveryConfig.L1BeaconNodeEndpoint == "" {
|
||||
return fmt.Errorf("L1 beacon node endpoint must be specified")
|
||||
}
|
||||
if cfg.RecoveryConfig.L1BlockHeight == 0 {
|
||||
return fmt.Errorf("L1 block height must be specified")
|
||||
}
|
||||
if cfg.RecoveryConfig.LatestFinalizedBatch == 0 {
|
||||
return fmt.Errorf("latest finalized batch must be specified")
|
||||
}
|
||||
|
||||
// init db connection
|
||||
db, err := database.InitDB(cfg.DBConfig)
|
||||
if err != nil {
|
||||
log.Crit("failed to init db connection", "err", err)
|
||||
}
|
||||
defer func() {
|
||||
if err = database.CloseDB(db); err != nil {
|
||||
log.Crit("failed to close db connection", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
registry := prometheus.DefaultRegisterer
|
||||
observability.Server(ctx, db)
|
||||
|
||||
genesisPath := ctx.String(utils.Genesis.Name)
|
||||
genesis, err := utils.ReadGenesis(genesisPath)
|
||||
if err != nil {
|
||||
log.Crit("failed to read genesis", "genesis file", genesisPath, "error", err)
|
||||
}
|
||||
|
||||
minCodecVersion := encoding.CodecVersion(ctx.Uint(utils.MinCodecVersionFlag.Name))
|
||||
chunkProposer := watcher.NewChunkProposer(subCtx, cfg.L2Config.ChunkProposerConfig, minCodecVersion, genesis.Config, db, registry)
|
||||
batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, minCodecVersion, genesis.Config, db, false, registry)
|
||||
bundleProposer := watcher.NewBundleProposer(subCtx, cfg.L2Config.BundleProposerConfig, minCodecVersion, genesis.Config, db, registry)
|
||||
|
||||
// Init l2geth connection
|
||||
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to L2geth at RPC=%s: %w", cfg.L2Config.Endpoint, err)
|
||||
}
|
||||
|
||||
l2Watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, registry)
|
||||
|
||||
recovery := permissionless_batches.NewRecovery(subCtx, cfg, genesis, db, chunkProposer, batchProposer, bundleProposer, l2Watcher)
|
||||
|
||||
if recovery.RecoveryNeeded() {
|
||||
if err = recovery.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run recovery: %w", err)
|
||||
}
|
||||
log.Info("Success! You're ready to generate proofs!")
|
||||
} else {
|
||||
log.Info("No recovery needed, submitting batch and proof to L1...")
|
||||
submitter, err := permissionless_batches.NewSubmitter(subCtx, db, cfg.L2Config.RelayerConfig, genesis.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create submitter: %w", err)
|
||||
}
|
||||
if err = submitter.Submit(!cfg.RecoveryConfig.SubmitWithoutProof); err != nil {
|
||||
return fmt.Errorf("failed to submit batch: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Transaction submitted to L1, waiting for confirmation...")
|
||||
|
||||
// Catch CTRL-C to ensure a graceful shutdown.
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
select {
|
||||
case <-subCtx.Done():
|
||||
case confirmation := <-submitter.Sender().ConfirmChan():
|
||||
if confirmation.IsSuccessful {
|
||||
log.Info("Transaction confirmed on L1, your permissionless batch is part of the ledger!", "tx hash", confirmation.TxHash)
|
||||
}
|
||||
case <-interrupt:
|
||||
log.Info("CTRL-C received, shutting down...")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run rollup relayer cmd instance.
|
||||
func Run() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
7
rollup/cmd/permissionless_batches/main.go
Normal file
7
rollup/cmd/permissionless_batches/main.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "scroll-tech/rollup/cmd/permissionless_batches/app"
|
||||
|
||||
func main() {
|
||||
app.Run()
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"scroll-tech/common/database"
|
||||
@@ -95,6 +96,9 @@ func action(ctx *cli.Context) error {
|
||||
if cfg.L2Config.ChunkProposerConfig.MaxL2GasPerChunk <= 0 {
|
||||
log.Crit("cfg.L2Config.ChunkProposerConfig.MaxL2GasPerChunk must be greater than 0")
|
||||
}
|
||||
if cfg.L2Config.RelayerConfig.SenderConfig.FusakaTimestamp == 0 {
|
||||
log.Crit("cfg.L2Config.RelayerConfig.SenderConfig.FusakaTimestamp must be set")
|
||||
}
|
||||
|
||||
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig, genesis.Config, relayer.ServiceTypeL2RollupRelayer, registry)
|
||||
if err != nil {
|
||||
@@ -112,6 +116,32 @@ func action(ctx *cli.Context) error {
|
||||
|
||||
l2watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, registry)
|
||||
|
||||
if cfg.RecoveryConfig != nil && cfg.RecoveryConfig.Enable {
|
||||
log.Info("Starting rollup-relayer in recovery mode", "version", version.Version)
|
||||
|
||||
l1Client, err := ethclient.Dial(cfg.L1Config.Endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to L1 client: %w", err)
|
||||
}
|
||||
reader, err := l1.NewReader(context.Background(), l1.Config{
|
||||
ScrollChainAddress: genesis.Config.Scroll.L1Config.ScrollChainAddress,
|
||||
L1MessageQueueAddress: genesis.Config.Scroll.L1Config.L1MessageQueueAddress,
|
||||
}, l1Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create L1 reader: %w", err)
|
||||
}
|
||||
|
||||
fullRecovery, err := relayer.NewFullRecovery(subCtx, cfg, genesis, db, chunkProposer, batchProposer, bundleProposer, l2watcher, l1Client, reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create full recovery: %w", err)
|
||||
}
|
||||
if err = fullRecovery.RestoreFullPreviousState(); err != nil {
|
||||
log.Crit("failed to restore full previous state", "error", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watcher loop to fetch missing blocks
|
||||
go utils.LoopWithContext(subCtx, 2*time.Second, func(ctx context.Context) {
|
||||
number, loopErr := rutils.GetLatestConfirmedBlockNumber(ctx, l2client, cfg.L2Config.Confirmations)
|
||||
@@ -119,7 +149,8 @@ func action(ctx *cli.Context) error {
|
||||
log.Error("failed to get block number", "err", loopErr)
|
||||
return
|
||||
}
|
||||
l2watcher.TryFetchRunningMissingBlocks(number)
|
||||
// errors are logged in the try method as well
|
||||
_ = l2watcher.TryFetchRunningMissingBlocks(number)
|
||||
})
|
||||
|
||||
go utils.Loop(subCtx, time.Duration(cfg.L2Config.ChunkProposerConfig.ProposeIntervalMilliseconds)*time.Millisecond, chunkProposer.TryProposeChunk)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"endpoint": "https://rpc.ankr.com/eth",
|
||||
"start_height": 0,
|
||||
"relayer_config": {
|
||||
"gas_price_oracle_address": "0x0000000000000000000000000000000000000000",
|
||||
"gas_price_oracle_contract_address": "0x0000000000000000000000000000000000000000",
|
||||
"sender_config": {
|
||||
"endpoint": "https://rpc.scroll.io",
|
||||
"escalate_blocks": 1,
|
||||
@@ -38,7 +38,6 @@
|
||||
"relayer_config": {
|
||||
"validium_mode": false,
|
||||
"rollup_contract_address": "0x0000000000000000000000000000000000000000",
|
||||
"gas_price_oracle_address": "0x0000000000000000000000000000000000000000",
|
||||
"sender_config": {
|
||||
"endpoint": "https://rpc.ankr.com/eth",
|
||||
"escalate_blocks": 1,
|
||||
@@ -50,7 +49,8 @@
|
||||
"tx_type": "DynamicFeeTx",
|
||||
"check_pending_time": 1,
|
||||
"min_gas_tip": 100000000,
|
||||
"max_pending_blob_txs": 3
|
||||
"max_pending_blob_txs": 3,
|
||||
"fusaka_timestamp": 9999999999999
|
||||
},
|
||||
"batch_submission": {
|
||||
"min_batches": 1,
|
||||
@@ -93,7 +93,6 @@
|
||||
},
|
||||
"chunk_proposer_config": {
|
||||
"propose_interval_milliseconds": 100,
|
||||
"max_block_num_per_chunk": 100,
|
||||
"max_l2_gas_per_chunk": 20000000,
|
||||
"chunk_timeout_sec": 300,
|
||||
"max_uncompressed_batch_bytes_size": 4194304
|
||||
|
||||
@@ -15,8 +15,8 @@ require (
|
||||
github.com/holiman/uint256 v1.3.2
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63
|
||||
github.com/smartystreets/goconvey v1.8.0
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
@@ -49,10 +49,11 @@ require (
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/consensys/bavard v0.1.29 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
|
||||
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
|
||||
github.com/fjl/memsize v0.0.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
@@ -118,7 +119,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/status-im/keycard-go v0.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/supranational/blst v0.3.13 // indirect
|
||||
github.com/supranational/blst v0.3.15 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
|
||||
@@ -79,6 +79,8 @@ github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw
|
||||
github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -91,8 +93,8 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vs
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
||||
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
@@ -285,10 +287,10 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6 h1:vb2XLvQwCf+F/ifP6P/lfeiQrHY6+Yb/E3R4KHXLqSE=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7 h1:1rN1qocsQlOyk1VCpIEF1J5pfQbLAi1pnMZSLQS37jQ=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7/go.mod h1:pDCZ4iGvEGmdIe4aSAGBrb7XSrKEML6/L/wEMmNxOdk=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178 h1:4utngmJHXSOS5FoSdZhEV1xMRirpArbXvyoCZY9nYj0=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250826112206-b4cce5c5d178/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63 h1:xuqdhD4w/zcI5T8Ty1wHvqB75P2HNg3jTH/kUEHGt9Y=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20251017054300-9aa8b3f38f63/go.mod h1:zRa7CnS75mFdgp8IeMtZV/wCAlxPRT33Ek3+fFbBJVQ=
|
||||
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
|
||||
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
@@ -330,8 +332,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk=
|
||||
github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/supranational/blst v0.3.15 h1:rd9viN6tfARE5wv3KZJ9H8e1cg0jXW8syFCcsbHa76o=
|
||||
github.com/supranational/blst v0.3.15/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
|
||||
@@ -18,9 +18,10 @@ import (
|
||||
|
||||
// Config load configuration items.
|
||||
type Config struct {
|
||||
L1Config *L1Config `json:"l1_config"`
|
||||
L2Config *L2Config `json:"l2_config"`
|
||||
DBConfig *database.Config `json:"db_config"`
|
||||
L1Config *L1Config `json:"l1_config"`
|
||||
L2Config *L2Config `json:"l2_config"`
|
||||
DBConfig *database.Config `json:"db_config"`
|
||||
RecoveryConfig *RecoveryConfig `json:"recovery_config"`
|
||||
}
|
||||
|
||||
type ConfigForReplay struct {
|
||||
|
||||
@@ -8,4 +8,7 @@ type L1Config struct {
|
||||
StartHeight uint64 `json:"start_height"`
|
||||
// The relayer config
|
||||
RelayerConfig *RelayerConfig `json:"relayer_config"`
|
||||
|
||||
// beacon node url
|
||||
BeaconNodeEndpoint string `json:"beacon_node_endpoint"`
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ type L2Config struct {
|
||||
// ChunkProposerConfig loads chunk_proposer configuration items.
|
||||
type ChunkProposerConfig struct {
|
||||
ProposeIntervalMilliseconds uint64 `json:"propose_interval_milliseconds"`
|
||||
MaxBlockNumPerChunk uint64 `json:"max_block_num_per_chunk"`
|
||||
MaxL2GasPerChunk uint64 `json:"max_l2_gas_per_chunk"`
|
||||
ChunkTimeoutSec uint64 `json:"chunk_timeout_sec"`
|
||||
MaxUncompressedBatchBytesSize uint64 `json:"max_uncompressed_batch_bytes_size"`
|
||||
|
||||
14
rollup/internal/config/recovery.go
Normal file
14
rollup/internal/config/recovery.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package config
|
||||
|
||||
type RecoveryConfig struct {
|
||||
Enable bool `json:"enable"`
|
||||
|
||||
L1BeaconNodeEndpoint string `json:"l1_beacon_node_endpoint"` // the L1 beacon node endpoint to connect to
|
||||
L1BlockHeight uint64 `json:"l1_block_height"` // the L1 block height to start recovery from
|
||||
LatestFinalizedBatch uint64 `json:"latest_finalized_batch"` // the latest finalized batch number
|
||||
L2BlockHeightLimit uint64 `json:"l2_block_height_limit"` // L2 block up to which to produce batch
|
||||
|
||||
ForceLatestFinalizedBatch bool `json:"force_latest_finalized_batch"` // whether to force usage of the latest finalized batch - mainly used for testing
|
||||
ForceL1MessageCount uint64 `json:"force_l1_message_count"` // force the number of L1 messages, useful for testing
|
||||
SubmitWithoutProof bool `json:"submit_without_proof"` // whether to submit batches without proof, useful for testing
|
||||
}
|
||||
@@ -7,8 +7,14 @@ import (
|
||||
|
||||
// SenderConfig The config for transaction sender
|
||||
type SenderConfig struct {
|
||||
// The RPC endpoint of the ethereum or scroll public node.
|
||||
// The RPC endpoint of the ethereum or scroll public node (for backward compatibility).
|
||||
// If WriteEndpoints is specified, this endpoint will be used only for reading.
|
||||
// If WriteEndpoints is empty, this endpoint will be used for both reading and writing.
|
||||
Endpoint string `json:"endpoint"`
|
||||
// The RPC endpoints to send transactions to (optional).
|
||||
// If specified, transactions will be sent to all these endpoints in parallel.
|
||||
// If empty, transactions will be sent to the Endpoint.
|
||||
WriteEndpoints []string `json:"write_endpoints,omitempty"`
|
||||
// The time to trigger check pending txs in sender.
|
||||
CheckPendingTime uint64 `json:"check_pending_time"`
|
||||
// The number of blocks to wait to escalate increase gas price of the transaction.
|
||||
@@ -29,6 +35,8 @@ type SenderConfig struct {
|
||||
TxType string `json:"tx_type"`
|
||||
// The maximum number of pending blob-carrying transactions
|
||||
MaxPendingBlobTxs int64 `json:"max_pending_blob_txs"`
|
||||
// The timestamp of the Ethereum Fusaka upgrade in seconds since epoch.
|
||||
FusakaTimestamp uint64 `json:"fusaka_timestamp"`
|
||||
}
|
||||
|
||||
type BatchSubmission struct {
|
||||
|
||||
@@ -167,7 +167,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er
|
||||
Chunks: chunks,
|
||||
}
|
||||
|
||||
case encoding.CodecV7:
|
||||
case encoding.CodecV7, encoding.CodecV8:
|
||||
encodingBatch = &encoding.Batch{
|
||||
Index: dbBatch.Index,
|
||||
ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash),
|
||||
|
||||
@@ -25,8 +25,6 @@ type S3Uploader struct {
|
||||
func NewS3Uploader(cfg *config.AWSS3Config) (*S3Uploader, error) {
|
||||
// load AWS config
|
||||
var opts []func(*awsconfig.LoadOptions) error
|
||||
opts = append(opts, awsconfig.WithRegion(cfg.Region))
|
||||
|
||||
// if AccessKey && SecretKey provided, use it
|
||||
if cfg.AccessKey != "" && cfg.SecretKey != "" {
|
||||
opts = append(opts, awsconfig.WithCredentialsProvider(
|
||||
@@ -38,6 +36,10 @@ func NewS3Uploader(cfg *config.AWSS3Config) (*S3Uploader, error) {
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.Region != "" {
|
||||
opts = append(opts, awsconfig.WithRegion(cfg.Region))
|
||||
}
|
||||
|
||||
awsCfg, err := awsconfig.LoadDefaultConfig(context.Background(), opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load default config: %w", err)
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
package permissionless_batches
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/blob_client"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
|
||||
"scroll-tech/database/migrate"
|
||||
|
||||
"scroll-tech/rollup/internal/config"
|
||||
"scroll-tech/rollup/internal/controller/watcher"
|
||||
"scroll-tech/rollup/internal/orm"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultFakeRestoredChunkIndex is the default index of the last restored fake chunk. It is used to be able to generate new chunks pretending that we have already processed some chunks.
|
||||
defaultFakeRestoredChunkIndex uint64 = 1337
|
||||
// defaultFakeRestoredBundleIndex is the default index of the last restored fake bundle. It is used to be able to generate new bundles pretending that we have already processed some bundles.
|
||||
defaultFakeRestoredBundleIndex uint64 = 1
|
||||
)
|
||||
|
||||
type MinimalRecovery struct {
|
||||
ctx context.Context
|
||||
cfg *config.Config
|
||||
genesis *core.Genesis
|
||||
db *gorm.DB
|
||||
chunkORM *orm.Chunk
|
||||
batchORM *orm.Batch
|
||||
bundleORM *orm.Bundle
|
||||
|
||||
chunkProposer *watcher.ChunkProposer
|
||||
batchProposer *watcher.BatchProposer
|
||||
bundleProposer *watcher.BundleProposer
|
||||
l2Watcher *watcher.L2WatcherClient
|
||||
}
|
||||
|
||||
func NewRecovery(ctx context.Context, cfg *config.Config, genesis *core.Genesis, db *gorm.DB, chunkProposer *watcher.ChunkProposer, batchProposer *watcher.BatchProposer, bundleProposer *watcher.BundleProposer, l2Watcher *watcher.L2WatcherClient) *MinimalRecovery {
|
||||
return &MinimalRecovery{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
genesis: genesis,
|
||||
db: db,
|
||||
chunkORM: orm.NewChunk(db),
|
||||
batchORM: orm.NewBatch(db),
|
||||
bundleORM: orm.NewBundle(db),
|
||||
chunkProposer: chunkProposer,
|
||||
batchProposer: batchProposer,
|
||||
bundleProposer: bundleProposer,
|
||||
l2Watcher: l2Watcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MinimalRecovery) RecoveryNeeded() bool {
|
||||
chunk, err := r.chunkORM.GetLatestChunk(r.ctx)
|
||||
if err != nil || chunk == nil {
|
||||
return true
|
||||
}
|
||||
if chunk.Index <= defaultFakeRestoredChunkIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
batch, err := r.batchORM.GetLatestBatch(r.ctx)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if batch.Index <= r.cfg.RecoveryConfig.LatestFinalizedBatch {
|
||||
return true
|
||||
}
|
||||
|
||||
bundle, err := r.bundleORM.GetLatestBundle(r.ctx)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if bundle.Index <= defaultFakeRestoredBundleIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *MinimalRecovery) Run() error {
|
||||
// Make sure we start from a clean state.
|
||||
if err := r.resetDB(); err != nil {
|
||||
return fmt.Errorf("failed to reset DB: %w", err)
|
||||
}
|
||||
|
||||
// Restore minimal previous state required to be able to create new chunks, batches and bundles.
|
||||
restoredFinalizedChunk, restoredFinalizedBatch, restoredFinalizedBundle, err := r.restoreMinimalPreviousState()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restore minimal previous state: %w", err)
|
||||
}
|
||||
|
||||
// Fetch and insert the missing blocks from the last block in the latestFinalizedBatch to the latest L2 block.
|
||||
fromBlock := restoredFinalizedChunk.EndBlockNumber
|
||||
toBlock, err := r.fetchL2Blocks(fromBlock, r.cfg.RecoveryConfig.L2BlockHeightLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch L2 blocks: %w", err)
|
||||
}
|
||||
|
||||
// Create chunks for L2 blocks.
|
||||
log.Info("Creating chunks for L2 blocks", "from", fromBlock, "to", toBlock)
|
||||
|
||||
var latestChunk *orm.Chunk
|
||||
var count int
|
||||
for {
|
||||
if err = r.chunkProposer.ProposeChunk(); err != nil {
|
||||
return fmt.Errorf("failed to propose chunk: %w", err)
|
||||
}
|
||||
count++
|
||||
|
||||
latestChunk, err = r.chunkORM.GetLatestChunk(r.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest latestFinalizedChunk: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Chunk created", "index", latestChunk.Index, "hash", latestChunk.Hash, "StartBlockNumber", latestChunk.StartBlockNumber, "EndBlockNumber", latestChunk.EndBlockNumber, "TotalL1MessagesPoppedBefore", latestChunk.TotalL1MessagesPoppedBefore)
|
||||
|
||||
// We have created chunks for all available L2 blocks.
|
||||
if latestChunk.EndBlockNumber >= toBlock {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Chunks created", "count", count, "latest Chunk", latestChunk.Index, "hash", latestChunk.Hash, "StartBlockNumber", latestChunk.StartBlockNumber, "EndBlockNumber", latestChunk.EndBlockNumber, "TotalL1MessagesPoppedBefore", latestChunk.TotalL1MessagesPoppedBefore, "PrevL1MessageQueueHash", latestChunk.PrevL1MessageQueueHash, "PostL1MessageQueueHash", latestChunk.PostL1MessageQueueHash)
|
||||
|
||||
// Create batch for the created chunks. We only allow 1 batch it needs to be submitted (and finalized) with a proof in a single step.
|
||||
log.Info("Creating batch for chunks", "from", restoredFinalizedChunk.Index+1, "to", latestChunk.Index)
|
||||
|
||||
r.batchProposer.TryProposeBatch()
|
||||
latestBatch, err := r.batchORM.GetLatestBatch(r.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest latestFinalizedBatch: %w", err)
|
||||
}
|
||||
|
||||
// Sanity check that the batch was created correctly:
|
||||
// 1. should be a new batch
|
||||
// 2. should contain all chunks created
|
||||
if restoredFinalizedBatch.Index+1 != latestBatch.Index {
|
||||
return fmt.Errorf("batch was not created correctly, expected %d but got %d", restoredFinalizedBatch.Index+1, latestBatch.Index)
|
||||
}
|
||||
|
||||
firstChunkInBatch, err := r.chunkORM.GetChunkByIndex(r.ctx, latestBatch.StartChunkIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get first chunk in batch: %w", err)
|
||||
}
|
||||
lastChunkInBatch, err := r.chunkORM.GetChunkByIndex(r.ctx, latestBatch.EndChunkIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get last chunk in batch: %w", err)
|
||||
}
|
||||
|
||||
// Make sure that the batch contains all previously created chunks and thus all blocks. If not the user will need to
|
||||
// produce another batch (running the application again) starting from the end block of the last chunk in the batch + 1.
|
||||
if latestBatch.EndChunkIndex != latestChunk.Index {
|
||||
log.Warn("Produced batch does not contain all chunks and blocks. You'll need to produce another batch starting from end block+1.", "starting block", firstChunkInBatch.StartBlockNumber, "end block", lastChunkInBatch.EndBlockNumber, "latest block", latestChunk.EndBlockNumber)
|
||||
}
|
||||
|
||||
log.Info("Batch created", "index", latestBatch.Index, "hash", latestBatch.Hash, "StartChunkIndex", latestBatch.StartChunkIndex, "EndChunkIndex", latestBatch.EndChunkIndex, "starting block", firstChunkInBatch.StartBlockNumber, "ending block", lastChunkInBatch.EndBlockNumber, "PrevL1MessageQueueHash", latestBatch.PrevL1MessageQueueHash, "PostL1MessageQueueHash", latestBatch.PostL1MessageQueueHash)
|
||||
|
||||
if err = r.bundleProposer.UpdateDBBundleInfo([]*orm.Batch{latestBatch}, encoding.CodecVersion(latestBatch.CodecVersion)); err != nil {
|
||||
return fmt.Errorf("failed to create bundle: %w", err)
|
||||
}
|
||||
|
||||
latestBundle, err := r.bundleORM.GetLatestBundle(r.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest bundle: %w", err)
|
||||
}
|
||||
|
||||
// Sanity check that the bundle was created correctly:
|
||||
// 1. should be a new bundle
|
||||
// 2. should only contain 1 batch, the one we created
|
||||
if restoredFinalizedBundle.Index == latestBundle.Index {
|
||||
return fmt.Errorf("bundle was not created correctly")
|
||||
}
|
||||
if latestBundle.StartBatchIndex != latestBatch.Index || latestBundle.EndBatchIndex != latestBatch.Index {
|
||||
return fmt.Errorf("bundle does not contain the correct batch: %d != %d", latestBundle.StartBatchIndex, latestBatch.Index)
|
||||
}
|
||||
|
||||
log.Info("Bundle created", "index", latestBundle.Index, "hash", latestBundle.Hash, "StartBatchIndex", latestBundle.StartBatchIndex, "EndBatchIndex", latestBundle.EndBatchIndex, "starting block", firstChunkInBatch.StartBlockNumber, "ending block", lastChunkInBatch.EndBlockNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreMinimalPreviousState restores the minimal previous state required to be able to create new chunks, batches and bundles.
|
||||
func (r *MinimalRecovery) restoreMinimalPreviousState() (*orm.Chunk, *orm.Batch, *orm.Bundle, error) {
|
||||
log.Info("Restoring previous state with", "L1 block height", r.cfg.RecoveryConfig.L1BlockHeight, "latest finalized batch", r.cfg.RecoveryConfig.LatestFinalizedBatch)
|
||||
|
||||
l1Client, err := ethclient.Dial(r.cfg.L1Config.Endpoint)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to connect to L1 client: %w", err)
|
||||
}
|
||||
reader, err := l1.NewReader(r.ctx, l1.Config{
|
||||
ScrollChainAddress: r.genesis.Config.Scroll.L1Config.ScrollChainAddress,
|
||||
L1MessageQueueAddress: r.genesis.Config.Scroll.L1Config.L1MessageQueueV2Address,
|
||||
}, l1Client)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to create L1 reader: %w", err)
|
||||
}
|
||||
|
||||
// 1. Sanity check user input: Make sure that the user's L1 block height is not higher than the latest finalized block number.
|
||||
latestFinalizedL1Block, err := reader.GetLatestFinalizedBlockNumber()
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to get latest finalized L1 block number: %w", err)
|
||||
}
|
||||
if r.cfg.RecoveryConfig.L1BlockHeight > latestFinalizedL1Block {
|
||||
return nil, nil, nil, fmt.Errorf("specified L1 block height is higher than the latest finalized block number: %d > %d", r.cfg.RecoveryConfig.L1BlockHeight, latestFinalizedL1Block)
|
||||
}
|
||||
|
||||
log.Info("Latest finalized L1 block number", "latest finalized L1 block", latestFinalizedL1Block)
|
||||
|
||||
// 2. Make sure that the specified batch is indeed finalized on the L1 rollup contract and is the latest finalized batch.
|
||||
var latestFinalizedBatchIndex uint64
|
||||
if r.cfg.RecoveryConfig.ForceLatestFinalizedBatch {
|
||||
latestFinalizedBatchIndex = r.cfg.RecoveryConfig.LatestFinalizedBatch
|
||||
} else {
|
||||
latestFinalizedBatchIndex, err = reader.LatestFinalizedBatchIndex(latestFinalizedL1Block)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to get latest finalized batch: %w", err)
|
||||
}
|
||||
if r.cfg.RecoveryConfig.LatestFinalizedBatch != latestFinalizedBatchIndex {
|
||||
return nil, nil, nil, fmt.Errorf("batch %d is not the latest finalized batch: %d", r.cfg.RecoveryConfig.LatestFinalizedBatch, latestFinalizedBatchIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// Find the commit event for the latest finalized batch.
|
||||
var batchCommitEvent *l1.CommitBatchEvent
|
||||
err = reader.FetchRollupEventsInRangeWithCallback(r.cfg.RecoveryConfig.L1BlockHeight, latestFinalizedL1Block, func(event l1.RollupEvent) bool {
|
||||
if event.Type() == l1.CommitEventType && event.BatchIndex().Uint64() == latestFinalizedBatchIndex {
|
||||
batchCommitEvent = event.(*l1.CommitBatchEvent)
|
||||
// We found the commit event for the batch, stop searching.
|
||||
return false
|
||||
}
|
||||
|
||||
// Continue until we find the commit event for the batch.
|
||||
return true
|
||||
})
|
||||
if batchCommitEvent == nil {
|
||||
return nil, nil, nil, fmt.Errorf("commit event not found for batch %d", latestFinalizedBatchIndex)
|
||||
}
|
||||
|
||||
log.Info("Found commit event for batch", "batch", batchCommitEvent.BatchIndex(), "hash", batchCommitEvent.BatchHash(), "L1 block height", batchCommitEvent.BlockNumber(), "L1 tx hash", batchCommitEvent.TxHash())
|
||||
|
||||
// 3. Fetch commit tx data for latest finalized batch and decode it.
|
||||
daBatch, daBlobPayload, err := r.decodeLatestFinalizedBatch(reader, batchCommitEvent)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to decode latest finalized batch: %w", err)
|
||||
}
|
||||
fmt.Println(daBatch, daBlobPayload)
|
||||
|
||||
blocksInBatch := daBlobPayload.Blocks()
|
||||
|
||||
if len(blocksInBatch) == 0 {
|
||||
return nil, nil, nil, fmt.Errorf("no blocks in batch %d", batchCommitEvent.BatchIndex())
|
||||
}
|
||||
lastBlockInBatch := blocksInBatch[len(blocksInBatch)-1]
|
||||
|
||||
log.Info("Last L2 block in batch", "batch", batchCommitEvent.BatchIndex(), "L2 block", lastBlockInBatch, "PostL1MessageQueueHash", daBlobPayload.PostL1MessageQueueHash())
|
||||
|
||||
// 4. Get the L1 messages count and state root after the latest finalized batch.
|
||||
var l1MessagesCount uint64
|
||||
if r.cfg.RecoveryConfig.ForceL1MessageCount == 0 {
|
||||
l1MessagesCount, err = reader.NextUnfinalizedL1MessageQueueIndex(latestFinalizedL1Block)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to get L1 messages count: %w", err)
|
||||
}
|
||||
} else {
|
||||
l1MessagesCount = r.cfg.RecoveryConfig.ForceL1MessageCount
|
||||
}
|
||||
|
||||
log.Info("L1 messages count after latest finalized batch", "batch", batchCommitEvent.BatchIndex(), "count", l1MessagesCount)
|
||||
|
||||
stateRoot, err := reader.GetFinalizedStateRootByBatchIndex(latestFinalizedL1Block, latestFinalizedBatchIndex)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to get state root: %w", err)
|
||||
}
|
||||
|
||||
log.Info("State root after latest finalized batch", "batch", batchCommitEvent.BatchIndex(), "stateRoot", stateRoot.Hex())
|
||||
|
||||
// 5. Insert minimal state to DB.
|
||||
chunk, err := r.chunkORM.InsertPermissionlessChunk(r.ctx, defaultFakeRestoredChunkIndex, daBatch.Version(), daBlobPayload, l1MessagesCount, stateRoot)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to insert chunk raw: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Inserted last finalized chunk to DB", "chunk", chunk.Index, "hash", chunk.Hash, "StartBlockNumber", chunk.StartBlockNumber, "EndBlockNumber", chunk.EndBlockNumber, "TotalL1MessagesPoppedBefore", chunk.TotalL1MessagesPoppedBefore)
|
||||
|
||||
batch, err := r.batchORM.InsertPermissionlessBatch(r.ctx, batchCommitEvent.BatchIndex(), batchCommitEvent.BatchHash(), daBatch.Version(), chunk)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to insert batch raw: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Inserted last finalized batch to DB", "batch", batch.Index, "hash", batch.Hash)
|
||||
|
||||
var bundle *orm.Bundle
|
||||
err = r.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
bundle, err = r.bundleORM.InsertBundle(r.ctx, []*orm.Batch{batch}, encoding.CodecVersion(batch.CodecVersion), dbTX)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert bundle: %w", err)
|
||||
}
|
||||
if err = r.bundleORM.UpdateProvingStatus(r.ctx, bundle.Hash, types.ProvingTaskVerified, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update proving status: %w", err)
|
||||
}
|
||||
if err = r.bundleORM.UpdateRollupStatus(r.ctx, bundle.Hash, types.RollupFinalized); err != nil {
|
||||
return fmt.Errorf("failed to update rollup status: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Inserted last finalized bundle to DB", "bundle", bundle.Index, "hash", bundle.Hash, "StartBatchIndex", bundle.StartBatchIndex, "EndBatchIndex", bundle.EndBatchIndex)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to insert bundle: %w", err)
|
||||
}
|
||||
return chunk, batch, bundle, nil
|
||||
}
|
||||
|
||||
func (r *MinimalRecovery) decodeLatestFinalizedBatch(reader *l1.Reader, event *l1.CommitBatchEvent) (encoding.DABatch, encoding.DABlobPayload, error) {
|
||||
blockHeader, err := reader.FetchBlockHeaderByNumber(event.BlockNumber())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get header by number, err: %w", err)
|
||||
}
|
||||
|
||||
args, err := reader.FetchCommitTxData(event)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to fetch commit tx data: %w", err)
|
||||
}
|
||||
|
||||
codecVersion := encoding.CodecVersion(args.Version)
|
||||
if codecVersion < encoding.CodecV7 {
|
||||
return nil, nil, fmt.Errorf("codec version %d is not supported", codecVersion)
|
||||
}
|
||||
|
||||
codec, err := encoding.CodecFromVersion(codecVersion)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get codec: %w", err)
|
||||
}
|
||||
|
||||
// Since we only store the last batch hash committed in a single tx in the contracts we can also only ever
|
||||
// finalize a last batch of a tx. This means we can assume here that the batch given in the event is the last batch
|
||||
// that was committed in the tx.
|
||||
|
||||
if event.BatchIndex().Uint64()+1 < uint64(len(args.BlobHashes)) {
|
||||
return nil, nil, fmt.Errorf("batch index %d+1 is lower than the number of blobs %d", event.BatchIndex().Uint64(), len(args.BlobHashes))
|
||||
}
|
||||
firstBatchIndex := event.BatchIndex().Uint64() + 1 - uint64(len(args.BlobHashes))
|
||||
|
||||
var targetBatch encoding.DABatch
|
||||
var targetBlobVersionedHash common.Hash
|
||||
parentBatchHash := args.ParentBatchHash
|
||||
for i, blobVersionedHash := range args.BlobHashes {
|
||||
batchIndex := firstBatchIndex + uint64(i)
|
||||
|
||||
calculatedBatch, err := codec.NewDABatchFromParams(batchIndex, blobVersionedHash, parentBatchHash)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create new DA batch from params, batch index: %d, err: %w", event.BatchIndex().Uint64(), err)
|
||||
}
|
||||
parentBatchHash = calculatedBatch.Hash()
|
||||
|
||||
if batchIndex == event.BatchIndex().Uint64() {
|
||||
if calculatedBatch.Hash() != event.BatchHash() {
|
||||
return nil, nil, fmt.Errorf("batch hash mismatch for batch %d, expected: %s, got: %s", event.BatchIndex(), event.BatchHash().String(), calculatedBatch.Hash().String())
|
||||
}
|
||||
// We found the batch we are looking for, break out of the loop.
|
||||
targetBatch = calculatedBatch
|
||||
targetBlobVersionedHash = blobVersionedHash
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetBatch == nil {
|
||||
return nil, nil, fmt.Errorf("target batch with index %d could not be found and decoded", event.BatchIndex())
|
||||
}
|
||||
|
||||
// sanity check that this is indeed the last batch in the tx
|
||||
if targetBatch.Hash() != args.LastBatchHash {
|
||||
return nil, nil, fmt.Errorf("last batch hash mismatch for batch %d, expected: %s, got: %s", event.BatchIndex(), args.LastBatchHash.String(), targetBatch.Hash().String())
|
||||
}
|
||||
|
||||
// TODO: add support for multiple blob clients
|
||||
blobClient := blob_client.NewBlobClients()
|
||||
if r.cfg.RecoveryConfig.L1BeaconNodeEndpoint != "" {
|
||||
client, err := blob_client.NewBeaconNodeClient(r.cfg.RecoveryConfig.L1BeaconNodeEndpoint)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create beacon node client: %w", err)
|
||||
}
|
||||
blobClient.AddBlobClient(client)
|
||||
}
|
||||
|
||||
log.Info("Fetching blob by versioned hash and block time", "TargetBlobVersionedHash", targetBlobVersionedHash, "BlockTime", blockHeader.Time, "BlockNumber", blockHeader.Number)
|
||||
blob, err := blobClient.GetBlobByVersionedHashAndBlockTime(r.ctx, targetBlobVersionedHash, blockHeader.Time)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get blob by versioned hash and block time for batch %d: %w", event.BatchIndex(), err)
|
||||
}
|
||||
|
||||
daBlobPayload, err := codec.DecodeBlob(blob)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to decode blob for batch %d: %w", event.BatchIndex(), err)
|
||||
}
|
||||
|
||||
return targetBatch, daBlobPayload, nil
|
||||
}
|
||||
|
||||
func (r *MinimalRecovery) fetchL2Blocks(fromBlock uint64, l2BlockHeightLimit uint64) (uint64, error) {
|
||||
if l2BlockHeightLimit > 0 && fromBlock > l2BlockHeightLimit {
|
||||
return 0, fmt.Errorf("fromBlock (latest finalized L2 block) is higher than specified L2BlockHeightLimit: %d > %d", fromBlock, l2BlockHeightLimit)
|
||||
}
|
||||
|
||||
log.Info("Fetching L2 blocks with", "fromBlock", fromBlock, "l2BlockHeightLimit", l2BlockHeightLimit)
|
||||
|
||||
// Fetch and insert the missing blocks from the last block in the batch to the latest L2 block.
|
||||
latestL2Block, err := r.l2Watcher.Client.BlockNumber(r.ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get latest L2 block number: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Latest L2 block number", "latest L2 block", latestL2Block)
|
||||
|
||||
if l2BlockHeightLimit > latestL2Block {
|
||||
return 0, fmt.Errorf("l2BlockHeightLimit is higher than the latest L2 block number, not all blocks are available in L2geth: %d > %d", l2BlockHeightLimit, latestL2Block)
|
||||
}
|
||||
|
||||
toBlock := latestL2Block
|
||||
if l2BlockHeightLimit > 0 {
|
||||
toBlock = l2BlockHeightLimit
|
||||
}
|
||||
|
||||
err = r.l2Watcher.GetAndStoreBlocks(r.ctx, fromBlock, toBlock)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get and store blocks: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Fetched L2 blocks from", "fromBlock", fromBlock, "toBlock", toBlock)
|
||||
|
||||
return toBlock, nil
|
||||
}
|
||||
|
||||
func (r *MinimalRecovery) resetDB() error {
|
||||
sqlDB, err := r.db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get db connection: %w", err)
|
||||
}
|
||||
|
||||
if err = migrate.ResetDB(sqlDB); err != nil {
|
||||
return fmt.Errorf("failed to reset db: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
265
rollup/internal/controller/permissionless_batches/submitter.go
Normal file
265
rollup/internal/controller/permissionless_batches/submitter.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package permissionless_batches
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/params"
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
"scroll-tech/common/types/message"
|
||||
|
||||
bridgeAbi "scroll-tech/rollup/abi"
|
||||
"scroll-tech/rollup/internal/config"
|
||||
"scroll-tech/rollup/internal/controller/sender"
|
||||
"scroll-tech/rollup/internal/orm"
|
||||
)
|
||||
|
||||
type Submitter struct {
|
||||
ctx context.Context
|
||||
|
||||
db *gorm.DB
|
||||
l2BlockOrm *orm.L2Block
|
||||
chunkOrm *orm.Chunk
|
||||
batchOrm *orm.Batch
|
||||
bundleOrm *orm.Bundle
|
||||
|
||||
cfg *config.RelayerConfig
|
||||
|
||||
finalizeSender *sender.Sender
|
||||
l1RollupABI *abi.ABI
|
||||
|
||||
chainCfg *params.ChainConfig
|
||||
}
|
||||
|
||||
func NewSubmitter(ctx context.Context, db *gorm.DB, cfg *config.RelayerConfig, chainCfg *params.ChainConfig) (*Submitter, error) {
|
||||
registry := prometheus.DefaultRegisterer
|
||||
finalizeSender, err := sender.NewSender(ctx, cfg.SenderConfig, cfg.FinalizeSenderSignerConfig, "permissionless_batches_submitter", "finalize_sender", types.SenderTypeFinalizeBatch, db, registry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new finalize sender failed, err: %w", err)
|
||||
}
|
||||
|
||||
return &Submitter{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
l2BlockOrm: orm.NewL2Block(db),
|
||||
chunkOrm: orm.NewChunk(db),
|
||||
batchOrm: orm.NewBatch(db),
|
||||
bundleOrm: orm.NewBundle(db),
|
||||
cfg: cfg,
|
||||
finalizeSender: finalizeSender,
|
||||
l1RollupABI: bridgeAbi.ScrollChainABI,
|
||||
chainCfg: chainCfg,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Submitter) Sender() *sender.Sender {
|
||||
return s.finalizeSender
|
||||
}
|
||||
|
||||
func (s *Submitter) Submit(withProof bool) error {
|
||||
// Check if the bundle is already finalized
|
||||
bundle, err := s.bundleOrm.GetLatestBundle(s.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading latest bundle: %w", err)
|
||||
}
|
||||
|
||||
if bundle.Index != defaultFakeRestoredBundleIndex+1 {
|
||||
return fmt.Errorf("unexpected bundle index %d with hash %s, expected %d", bundle.Index, bundle.Hash, defaultFakeRestoredBundleIndex+1)
|
||||
}
|
||||
|
||||
if types.RollupStatus(bundle.RollupStatus) == types.RollupFinalized {
|
||||
return fmt.Errorf("bundle %d %s is already finalized. nothing to do", bundle.Index, bundle.Hash)
|
||||
}
|
||||
|
||||
if bundle.StartBatchIndex != bundle.EndBatchIndex {
|
||||
return fmt.Errorf("bundle %d %s has unexpected batch indices (should only contain a single batch): start %d, end %d", bundle.Index, bundle.Hash, bundle.StartBatchIndex, bundle.EndBatchIndex)
|
||||
}
|
||||
if bundle.StartBatchHash != bundle.EndBatchHash {
|
||||
return fmt.Errorf("bundle %d %s has unexpected batch hashes (should only contain a single batch): start %s, end %s", bundle.Index, bundle.Hash, bundle.StartBatchHash, bundle.EndBatchHash)
|
||||
}
|
||||
|
||||
batch, err := s.batchOrm.GetBatchByIndex(s.ctx, bundle.StartBatchIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load batch %d: %w", bundle.StartBatchIndex, err)
|
||||
}
|
||||
if batch == nil {
|
||||
return fmt.Errorf("batch %d not found", bundle.StartBatchIndex)
|
||||
}
|
||||
if batch.Hash != bundle.StartBatchHash {
|
||||
return fmt.Errorf("bundle %d %s has unexpected batch hash: %s", bundle.Index, bundle.Hash, batch.Hash)
|
||||
}
|
||||
|
||||
log.Info("submitting batch", "index", batch.Index, "hash", batch.Hash)
|
||||
|
||||
endChunk, err := s.chunkOrm.GetChunkByIndex(s.ctx, batch.EndChunkIndex)
|
||||
if err != nil || endChunk == nil {
|
||||
return fmt.Errorf("failed to get end chunk with index %d of batch: %w", batch.EndChunkIndex, err)
|
||||
}
|
||||
|
||||
var aggProof *message.OpenVMBundleProof
|
||||
if withProof {
|
||||
firstChunk, err := s.chunkOrm.GetChunkByIndex(s.ctx, batch.StartChunkIndex)
|
||||
if err != nil || firstChunk == nil {
|
||||
return fmt.Errorf("failed to get first chunk %d of batch: %w", batch.StartChunkIndex, err)
|
||||
}
|
||||
|
||||
aggProof, err = s.bundleOrm.GetVerifiedProofByHash(s.ctx, bundle.Hash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get verified proof by bundle index: %d, err: %w", bundle.Index, err)
|
||||
}
|
||||
|
||||
if err = aggProof.SanityCheck(); err != nil {
|
||||
return fmt.Errorf("failed to check agg_proof sanity, index: %d, err: %w", bundle.Index, err)
|
||||
}
|
||||
}
|
||||
|
||||
var calldata []byte
|
||||
var blob *kzg4844.Blob
|
||||
switch encoding.CodecVersion(bundle.CodecVersion) {
|
||||
case encoding.CodecV7:
|
||||
calldata, blob, err = s.constructCommitAndFinalizeCalldataAndBlob(batch, endChunk, aggProof)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to construct CommitAndFinalize calldata and blob, bundle index: %v, batch index: %v, err: %w", bundle.Index, batch.Index, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported codec version in finalizeBundle, bundle index: %v, version: %d", bundle.Index, bundle.CodecVersion)
|
||||
}
|
||||
|
||||
txHash, _, err := s.finalizeSender.SendTransaction("commitAndFinalize-"+bundle.Hash, &s.cfg.RollupContractAddress, calldata, []*kzg4844.Blob{blob})
|
||||
if err != nil {
|
||||
log.Error("commitAndFinalize in layer1 failed", "with proof", withProof, "index", bundle.Index,
|
||||
"batch index", bundle.StartBatchIndex,
|
||||
"RollupContractAddress", s.cfg.RollupContractAddress, "err", err, "calldata", common.Bytes2Hex(calldata))
|
||||
|
||||
var rpcError rpc.DataError
|
||||
if errors.As(err, &rpcError) {
|
||||
log.Error("rpc.DataError ", "error", rpcError.Error(), "message", rpcError.ErrorData())
|
||||
}
|
||||
|
||||
return fmt.Errorf("commitAndFinalize failed, bundle index: %d, err: %w", bundle.Index, err)
|
||||
}
|
||||
|
||||
log.Info("commitAndFinalize in layer1", "with proof", withProof, "batch index", bundle.StartBatchIndex, "tx hash", txHash.String())
|
||||
|
||||
// Updating rollup status in database.
|
||||
err = s.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
if err = s.batchOrm.UpdateFinalizeTxHashAndRollupStatusByBundleHash(s.ctx, bundle.Hash, txHash.String(), types.RollupFinalizing, dbTX); err != nil {
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatusByBundleHash failed", "index", bundle.Index, "bundle hash", bundle.Hash, "tx hash", txHash.String(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.bundleOrm.UpdateFinalizeTxHashAndRollupStatus(s.ctx, bundle.Hash, txHash.String(), types.RollupFinalizing, dbTX); err != nil {
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed", "index", bundle.Index, "bundle hash", bundle.Hash, "tx hash", txHash.String(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("failed to update rollup status of bundle and batches", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Updating the proving status when finalizing without proof, thus the coordinator could omit this task.
|
||||
// it isn't a necessary step, so don't put in a transaction with UpdateFinalizeTxHashAndRollupStatus
|
||||
if !withProof {
|
||||
txErr := s.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
if updateErr := s.bundleOrm.UpdateProvingStatus(s.ctx, bundle.Hash, types.ProvingTaskVerified, dbTX); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if updateErr := s.batchOrm.UpdateProvingStatusByBundleHash(s.ctx, bundle.Hash, types.ProvingTaskVerified, dbTX); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
for batchIndex := bundle.StartBatchIndex; batchIndex <= bundle.EndBatchIndex; batchIndex++ {
|
||||
tmpBatch, getErr := s.batchOrm.GetBatchByIndex(s.ctx, batchIndex)
|
||||
if getErr != nil {
|
||||
return getErr
|
||||
}
|
||||
if updateErr := s.chunkOrm.UpdateProvingStatusByBatchHash(s.ctx, tmpBatch.Hash, types.ProvingTaskVerified, dbTX); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
log.Error("Updating chunk and batch proving status when finalizing without proof failure", "bundleHash", bundle.Hash, "err", txErr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Submitter) constructCommitAndFinalizeCalldataAndBlob(batch *orm.Batch, endChunk *orm.Chunk, aggProof *message.OpenVMBundleProof) ([]byte, *kzg4844.Blob, error) {
|
||||
// Create the FinalizeStruct tuple as an abi-compatible struct
|
||||
finalizeStruct := struct {
|
||||
BatchHeader []byte
|
||||
TotalL1MessagesPoppedOverall *big.Int
|
||||
PostStateRoot common.Hash
|
||||
WithdrawRoot common.Hash
|
||||
ZkProof []byte
|
||||
}{
|
||||
BatchHeader: batch.BatchHeader,
|
||||
TotalL1MessagesPoppedOverall: new(big.Int).SetUint64(endChunk.TotalL1MessagesPoppedBefore + endChunk.TotalL1MessagesPoppedInChunk),
|
||||
PostStateRoot: common.HexToHash(batch.StateRoot),
|
||||
WithdrawRoot: common.HexToHash(batch.WithdrawRoot),
|
||||
}
|
||||
if aggProof != nil {
|
||||
finalizeStruct.ZkProof = aggProof.Proof()
|
||||
}
|
||||
|
||||
calldata, err := s.l1RollupABI.Pack("commitAndFinalizeBatch", uint8(batch.CodecVersion), common.HexToHash(batch.ParentBatchHash), finalizeStruct)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to pack commitAndFinalizeBatch: %w", err)
|
||||
}
|
||||
|
||||
chunks, err := s.chunkOrm.GetChunksInRange(s.ctx, batch.StartChunkIndex, batch.EndChunkIndex)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get chunks in range for batch %d: %w", batch.Index, err)
|
||||
}
|
||||
if chunks[len(chunks)-1].Index != batch.EndChunkIndex {
|
||||
return nil, nil, fmt.Errorf("unexpected last chunk index %d, expected %d", chunks[len(chunks)-1].Index, batch.EndChunkIndex)
|
||||
}
|
||||
|
||||
var batchBlocks []*encoding.Block
|
||||
for _, c := range chunks {
|
||||
blocks, err := s.l2BlockOrm.GetL2BlocksInRange(s.ctx, c.StartBlockNumber, c.EndBlockNumber)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", batch.Index, err)
|
||||
}
|
||||
|
||||
batchBlocks = append(batchBlocks, blocks...)
|
||||
}
|
||||
|
||||
encodingBatch := &encoding.Batch{
|
||||
Index: batch.Index,
|
||||
ParentBatchHash: common.HexToHash(batch.ParentBatchHash),
|
||||
PrevL1MessageQueueHash: common.HexToHash(batch.PrevL1MessageQueueHash),
|
||||
PostL1MessageQueueHash: common.HexToHash(batch.PostL1MessageQueueHash),
|
||||
Blocks: batchBlocks,
|
||||
}
|
||||
|
||||
codec, err := encoding.CodecFromVersion(encoding.CodecVersion(batch.CodecVersion))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get codec from version %d, err: %w", batch.CodecVersion, err)
|
||||
}
|
||||
|
||||
daBatch, err := codec.NewDABatch(encodingBatch)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create DA batch: %w", err)
|
||||
}
|
||||
|
||||
return calldata, daBatch.Blob(), nil
|
||||
}
|
||||
476
rollup/internal/controller/relayer/full_recovery.go
Normal file
476
rollup/internal/controller/relayer/full_recovery.go
Normal file
@@ -0,0 +1,476 @@
|
||||
package relayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/blob_client"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
|
||||
"scroll-tech/rollup/internal/config"
|
||||
"scroll-tech/rollup/internal/controller/watcher"
|
||||
"scroll-tech/rollup/internal/orm"
|
||||
butils "scroll-tech/rollup/internal/utils"
|
||||
)
|
||||
|
||||
type FullRecovery struct {
|
||||
ctx context.Context
|
||||
cfg *config.Config
|
||||
genesis *core.Genesis
|
||||
db *gorm.DB
|
||||
blockORM *orm.L2Block
|
||||
chunkORM *orm.Chunk
|
||||
batchORM *orm.Batch
|
||||
bundleORM *orm.Bundle
|
||||
|
||||
chunkProposer *watcher.ChunkProposer
|
||||
batchProposer *watcher.BatchProposer
|
||||
bundleProposer *watcher.BundleProposer
|
||||
l2Watcher *watcher.L2WatcherClient
|
||||
l1Client *ethclient.Client
|
||||
l1Reader *l1.Reader
|
||||
beaconNodeClient *blob_client.BeaconNodeClient
|
||||
}
|
||||
|
||||
func NewFullRecovery(ctx context.Context, cfg *config.Config, genesis *core.Genesis, db *gorm.DB, chunkProposer *watcher.ChunkProposer, batchProposer *watcher.BatchProposer, bundleProposer *watcher.BundleProposer, l2Watcher *watcher.L2WatcherClient, l1Client *ethclient.Client, l1Reader *l1.Reader) (*FullRecovery, error) {
|
||||
beaconNodeClient, err := blob_client.NewBeaconNodeClient(cfg.L1Config.BeaconNodeEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create blob client failed: %v", err)
|
||||
}
|
||||
|
||||
return &FullRecovery{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
genesis: genesis,
|
||||
db: db,
|
||||
blockORM: orm.NewL2Block(db),
|
||||
chunkORM: orm.NewChunk(db),
|
||||
batchORM: orm.NewBatch(db),
|
||||
bundleORM: orm.NewBundle(db),
|
||||
|
||||
chunkProposer: chunkProposer,
|
||||
batchProposer: batchProposer,
|
||||
bundleProposer: bundleProposer,
|
||||
l2Watcher: l2Watcher,
|
||||
l1Client: l1Client,
|
||||
l1Reader: l1Reader,
|
||||
beaconNodeClient: beaconNodeClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RestoreFullPreviousState restores the full state from L1.
|
||||
// The DB state should be clean: the latest batch in the DB should be finalized on L1. This function will
|
||||
// restore all batches between the latest finalized batch in the DB and the latest finalized batch on L1.
|
||||
func (f *FullRecovery) RestoreFullPreviousState() error {
|
||||
log.Info("Restoring full previous state")
|
||||
|
||||
// 1. Get latest finalized batch stored in DB
|
||||
latestDBBatch, err := f.batchORM.GetLatestBatch(f.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest batch from DB: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Latest finalized batch in DB", "batch", latestDBBatch.Index, "hash", latestDBBatch.Hash)
|
||||
|
||||
// 2. Get latest finalized L1 block
|
||||
latestFinalizedL1Block, err := f.l1Reader.GetLatestFinalizedBlockNumber()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest finalized L1 block number: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Latest finalized L1 block number", "latest finalized L1 block", latestFinalizedL1Block)
|
||||
|
||||
// 3. Get latest finalized batch from contract (at latest finalized L1 block)
|
||||
latestFinalizedBatchContract, err := f.l1Reader.LatestFinalizedBatchIndex(latestFinalizedL1Block)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest finalized batch: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Latest finalized batch from L1 contract", "latest finalized batch", latestFinalizedBatchContract, "at latest finalized L1 block", latestFinalizedL1Block)
|
||||
|
||||
// 4. Get batches one by one from stored in DB to latest finalized batch.
|
||||
var fromBlock uint64
|
||||
if latestDBBatch.Index > 0 {
|
||||
receipt, err := f.l1Client.TransactionReceipt(f.ctx, common.HexToHash(latestDBBatch.CommitTxHash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get transaction receipt of latest DB batch finalization transaction: %w", err)
|
||||
}
|
||||
fromBlock = receipt.BlockNumber.Uint64()
|
||||
} else {
|
||||
fromBlock = f.cfg.L1Config.StartHeight
|
||||
}
|
||||
|
||||
log.Info("Fetching rollup events from L1", "from block", fromBlock, "to block", latestFinalizedL1Block, "from batch", latestDBBatch.Index, "to batch", latestFinalizedBatchContract)
|
||||
|
||||
commitsHeapMap := common.NewHeapMap[uint64, *l1.CommitBatchEvent](func(event *l1.CommitBatchEvent) uint64 {
|
||||
return event.BatchIndex().Uint64()
|
||||
})
|
||||
batchEventsHeap := common.NewHeap[*batchEvents]()
|
||||
var bundles [][]*batchEvents
|
||||
|
||||
err = f.l1Reader.FetchRollupEventsInRangeWithCallback(fromBlock, latestFinalizedL1Block, func(event l1.RollupEvent) bool {
|
||||
// We're only interested in batches that are newer than the latest finalized batch in the DB.
|
||||
if event.BatchIndex().Uint64() <= latestDBBatch.Index {
|
||||
return true
|
||||
}
|
||||
|
||||
switch event.Type() {
|
||||
case l1.CommitEventType:
|
||||
commitEvent := event.(*l1.CommitBatchEvent)
|
||||
commitsHeapMap.Push(commitEvent)
|
||||
|
||||
case l1.FinalizeEventType:
|
||||
finalizeEvent := event.(*l1.FinalizeBatchEvent)
|
||||
|
||||
var bundle []*batchEvents
|
||||
|
||||
// with bundles all committed batches until this finalized batch are finalized in the same bundle
|
||||
for commitsHeapMap.Len() > 0 {
|
||||
commitEvent := commitsHeapMap.Peek()
|
||||
if commitEvent.BatchIndex().Uint64() > finalizeEvent.BatchIndex().Uint64() {
|
||||
break
|
||||
}
|
||||
|
||||
bEvents := newBatchEvents(commitEvent, finalizeEvent)
|
||||
commitsHeapMap.Pop()
|
||||
batchEventsHeap.Push(bEvents)
|
||||
bundle = append(bundle, bEvents)
|
||||
}
|
||||
|
||||
bundles = append(bundles, bundle)
|
||||
|
||||
// Stop fetching rollup events if we reached the latest finalized batch.
|
||||
if finalizeEvent.BatchIndex().Uint64() >= latestFinalizedBatchContract {
|
||||
return false
|
||||
}
|
||||
case l1.RevertEventV0Type:
|
||||
// We ignore reverted batches.
|
||||
commitsHeapMap.RemoveByKey(event.BatchIndex().Uint64())
|
||||
case l1.RevertEventV7Type:
|
||||
// We ignore reverted batches.
|
||||
|
||||
revertBatch, ok := event.(*l1.RevertBatchEventV7)
|
||||
if !ok {
|
||||
log.Error(fmt.Sprintf("unexpected type of revert event: %T, expected RevertEventV7Type", event))
|
||||
return false
|
||||
}
|
||||
|
||||
// delete all batches from revertBatch.StartBatchIndex (inclusive) to revertBatch.FinishBatchIndex (inclusive)
|
||||
for i := revertBatch.StartBatchIndex().Uint64(); i <= revertBatch.FinishBatchIndex().Uint64(); i++ {
|
||||
commitsHeapMap.RemoveByKey(i)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch rollup events: %w", err)
|
||||
}
|
||||
|
||||
// 5. Process all finalized batches: fetch L2 blocks and reproduce chunks and batches.
|
||||
var batches []*batchEvents
|
||||
for batchEventsHeap.Len() > 0 {
|
||||
nextBatch := batchEventsHeap.Pop().Value()
|
||||
batches = append(batches, nextBatch)
|
||||
}
|
||||
|
||||
if err = f.processFinalizedBatches(batches); err != nil {
|
||||
return fmt.Errorf("failed to process finalized batches: %w", err)
|
||||
}
|
||||
|
||||
// 6. Create bundles if needed.
|
||||
for _, bundle := range bundles {
|
||||
var dbBatches []*orm.Batch
|
||||
var lastBatchInBundle *orm.Batch
|
||||
|
||||
for _, batch := range bundle {
|
||||
dbBatch, err := f.batchORM.GetBatchByIndex(f.ctx, batch.commit.BatchIndex().Uint64())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get batch by index for bundle generation: %w", err)
|
||||
}
|
||||
// Bundles are only supported for codec version 3 and above.
|
||||
if encoding.CodecVersion(dbBatch.CodecVersion) < encoding.CodecV3 {
|
||||
break
|
||||
}
|
||||
|
||||
dbBatches = append(dbBatches, dbBatch)
|
||||
lastBatchInBundle = dbBatch
|
||||
}
|
||||
|
||||
if len(dbBatches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err = f.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
newBundle, err := f.bundleORM.InsertBundle(f.ctx, dbBatches, encoding.CodecVersion(lastBatchInBundle.CodecVersion), dbTX)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert bundle to DB: %w", err)
|
||||
}
|
||||
if err = f.batchORM.UpdateBundleHashInRange(f.ctx, newBundle.StartBatchIndex, newBundle.EndBatchIndex, newBundle.Hash, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update bundle_hash %s for batches (%d to %d): %w", newBundle.Hash, newBundle.StartBatchIndex, newBundle.EndBatchIndex, err)
|
||||
}
|
||||
|
||||
if err = f.bundleORM.UpdateFinalizeTxHashAndRollupStatus(f.ctx, newBundle.Hash, lastBatchInBundle.FinalizeTxHash, types.RollupFinalized, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update finalize tx hash and rollup status for bundle %s: %w", newBundle.Hash, err)
|
||||
}
|
||||
|
||||
if err = f.bundleORM.UpdateProvingStatus(f.ctx, newBundle.Hash, types.ProvingTaskVerified, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update proving status for bundle %s: %w", newBundle.Hash, err)
|
||||
}
|
||||
|
||||
log.Info("Inserted bundle", "hash", newBundle.Hash, "start batch index", newBundle.StartBatchIndex, "end batch index", newBundle.EndBatchIndex)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert bundle in DB transaction: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FullRecovery) processFinalizedBatches(batches []*batchEvents) error {
|
||||
if len(batches) == 0 {
|
||||
return fmt.Errorf("no finalized batches to process")
|
||||
}
|
||||
|
||||
firstBatch := batches[0]
|
||||
lastBatch := batches[len(batches)-1]
|
||||
|
||||
log.Info("Processing finalized batches", "first batch", firstBatch.commit.BatchIndex(), "hash", firstBatch.commit.BatchHash(), "last batch", lastBatch.commit.BatchIndex(), "hash", lastBatch.commit.BatchHash())
|
||||
|
||||
// Since multiple CommitBatch events per transaction is introduced >= CodecV7,
|
||||
// with one transaction carrying multiple blobs,
|
||||
// each CommitBatch event corresponds to a blob containing block range data.
|
||||
// To correctly process these events, we need to:
|
||||
// 1. Parsing the associated blob data to extract the block range for each event
|
||||
// 2. Tracking the parent batch hash for each processed CommitBatch event, to:
|
||||
// - Validate the batch hash, since parent batch hash is needed to calculate the batch hash
|
||||
// - Derive the index of the current batch by the number of parent batch hashes tracked
|
||||
// In commitBatches and commitAndFinalizeBatch, the parent batch hash is passed in calldata,
|
||||
// so that we can use it to get the first batch's parent batch hash, and derive the rest.
|
||||
// The index map serves this purpose with:
|
||||
// Key: commit transaction hash
|
||||
// Value: parent batch hashes (in order) for each processed CommitBatch event in the transaction
|
||||
txBlobIndexMap := make(map[common.Hash][]common.Hash)
|
||||
for _, b := range batches {
|
||||
args, err := f.l1Reader.FetchCommitTxData(b.commit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch commit tx data of batch %d, tx hash: %v, err: %w", b.commit.BatchIndex().Uint64(), b.commit.TxHash().Hex(), err)
|
||||
}
|
||||
|
||||
// all batches we process here will be > CodecV7 since that is the minimum codec version for permissionless batches
|
||||
if args.Version < 7 {
|
||||
return fmt.Errorf("unsupported codec version: %v, batch index: %v, tx hash: %s", args.Version, b.commit.BatchIndex().Uint64(), b.commit.TxHash().Hex())
|
||||
}
|
||||
|
||||
codec, err := encoding.CodecFromVersion(encoding.CodecVersion(args.Version))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unsupported codec version: %v, err: %w", args.Version, err)
|
||||
}
|
||||
|
||||
// we append the batch hash to the slice for the current commit transaction after processing the batch.
|
||||
// that means the current index of the batch within the transaction is len(txBlobIndexMap[vlog.TxHash]).
|
||||
currentIndex := len(txBlobIndexMap[b.commit.TxHash()])
|
||||
if currentIndex >= len(args.BlobHashes) {
|
||||
return fmt.Errorf("commit transaction %s has %d blobs, but trying to access index %d (batch index %d)",
|
||||
b.commit.TxHash(), len(args.BlobHashes), currentIndex, b.commit.BatchIndex().Uint64())
|
||||
}
|
||||
blobVersionedHash := args.BlobHashes[currentIndex]
|
||||
|
||||
// validate the batch hash
|
||||
var parentBatchHash common.Hash
|
||||
if currentIndex == 0 {
|
||||
parentBatchHash = args.ParentBatchHash
|
||||
} else {
|
||||
// here we need to subtract 1 from the current index to get the parent batch hash.
|
||||
parentBatchHash = txBlobIndexMap[b.commit.TxHash()][currentIndex-1]
|
||||
}
|
||||
calculatedBatch, err := codec.NewDABatchFromParams(b.commit.BatchIndex().Uint64(), blobVersionedHash, parentBatchHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new DA batch from params, batch index: %d, err: %w", b.commit.BatchIndex().Uint64(), err)
|
||||
}
|
||||
if calculatedBatch.Hash() != b.commit.BatchHash() {
|
||||
return fmt.Errorf("batch hash mismatch for batch %d, expected: %s, got: %s", b.commit.BatchIndex(), b.commit.BatchHash().String(), calculatedBatch.Hash().String())
|
||||
}
|
||||
|
||||
txBlobIndexMap[b.commit.TxHash()] = append(txBlobIndexMap[b.commit.TxHash()], b.commit.BatchHash())
|
||||
|
||||
if err = f.insertBatchIntoDB(b, codec, blobVersionedHash); err != nil {
|
||||
return fmt.Errorf("failed to insert batch into DB, batch index: %d, err: %w", b.commit.BatchIndex().Uint64(), err)
|
||||
}
|
||||
|
||||
log.Info("Processed batch", "index", b.commit.BatchIndex(), "hash", b.commit.BatchHash(), "commit tx hash", b.commit.TxHash().Hex(), "finalize tx hash", b.finalize.TxHash().Hex(), "blob versioned hash", blobVersionedHash.String(), "parent batch hash", parentBatchHash.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FullRecovery) insertBatchIntoDB(batch *batchEvents, codec encoding.Codec, blobVersionedHash common.Hash) error {
|
||||
// 5.1 Fetch block time.
|
||||
blockHeader, err := f.l1Reader.FetchBlockHeaderByNumber(batch.commit.BlockNumber())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch block header by number %d: %w", batch.commit.BlockNumber(), err)
|
||||
}
|
||||
|
||||
// 5.2 Fetch blob data for batch.
|
||||
daBlocks, err := f.getBatchBlockRangeFromBlob(codec, blobVersionedHash, blockHeader.Time)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get batch block range from blob %s: %w", blobVersionedHash.Hex(), err)
|
||||
}
|
||||
lastBlock := daBlocks[len(daBlocks)-1]
|
||||
|
||||
// 5.2. Fetch L2 blocks for the entire batch.
|
||||
if err = f.l2Watcher.TryFetchRunningMissingBlocks(lastBlock.Number()); err != nil {
|
||||
return fmt.Errorf("failed to fetch L2 blocks: %w", err)
|
||||
}
|
||||
|
||||
// 5.3. Reproduce chunk. Since we don't know the internals of a batch we just create 1 chunk per batch.
|
||||
start := daBlocks[0].Number()
|
||||
end := lastBlock.Number()
|
||||
|
||||
// get last chunk from DB
|
||||
lastChunk, err := f.chunkORM.GetLatestChunk(f.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest chunk from DB: %w", err)
|
||||
}
|
||||
|
||||
blocks, err := f.blockORM.GetL2BlocksInRange(f.ctx, start, end)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get L2 blocks in range: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Reproducing chunk", "start block", start, "end block", end)
|
||||
|
||||
var chunk encoding.Chunk
|
||||
chunk.Blocks = blocks
|
||||
chunk.PrevL1MessageQueueHash = common.HexToHash(lastChunk.PostL1MessageQueueHash)
|
||||
chunk.PostL1MessageQueueHash, err = encoding.MessageQueueV2ApplyL1MessagesFromBlocks(chunk.PrevL1MessageQueueHash, blocks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply L1 messages from blocks: %w", err)
|
||||
}
|
||||
|
||||
metrics, err := butils.CalculateChunkMetrics(&chunk, codec.Version())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate chunk metrics: %w", err)
|
||||
}
|
||||
|
||||
var dbChunk *orm.Chunk
|
||||
err = f.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
dbChunk, err = f.chunkORM.InsertChunk(f.ctx, &chunk, codec.Version(), *metrics, dbTX)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert chunk to DB: %w", err)
|
||||
}
|
||||
if err := f.blockORM.UpdateChunkHashInRange(f.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber, dbChunk.Hash, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update chunk_hash for l2_blocks (chunk hash: %s, start block: %d, end block: %d): %w", dbChunk.Hash, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber, err)
|
||||
}
|
||||
|
||||
if err = f.chunkORM.UpdateProvingStatus(f.ctx, dbChunk.Hash, types.ProvingTaskVerified, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update proving status for chunk %s: %w", dbChunk.Hash, err)
|
||||
}
|
||||
|
||||
log.Info("Inserted chunk", "index", dbChunk.Index, "hash", dbChunk.Hash, "start block", dbChunk.StartBlockNumber, "end block", dbChunk.EndBlockNumber)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert chunk in DB transaction: %w", err)
|
||||
}
|
||||
|
||||
// 5.4 Reproduce batch.
|
||||
dbParentBatch, err := f.batchORM.GetLatestBatch(f.ctx)
|
||||
if err != nil || dbParentBatch == nil {
|
||||
return fmt.Errorf("failed to get latest batch from DB: %w", err)
|
||||
}
|
||||
|
||||
var encBatch encoding.Batch
|
||||
encBatch.Index = dbParentBatch.Index + 1
|
||||
encBatch.ParentBatchHash = common.HexToHash(dbParentBatch.Hash)
|
||||
encBatch.TotalL1MessagePoppedBefore = dbChunk.TotalL1MessagesPoppedBefore
|
||||
encBatch.PrevL1MessageQueueHash = chunk.PrevL1MessageQueueHash
|
||||
encBatch.PostL1MessageQueueHash = chunk.PostL1MessageQueueHash
|
||||
encBatch.Chunks = []*encoding.Chunk{&chunk}
|
||||
encBatch.Blocks = blocks
|
||||
|
||||
batchMetrics, err := butils.CalculateBatchMetrics(&encBatch, codec.Version(), false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate batch metrics: %w", err)
|
||||
}
|
||||
|
||||
err = f.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
dbBatch, err := f.batchORM.InsertBatch(f.ctx, &encBatch, codec.Version(), *batchMetrics, dbTX)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert batch to DB: %w", err)
|
||||
}
|
||||
if err = f.chunkORM.UpdateBatchHashInRange(f.ctx, dbBatch.StartChunkIndex, dbBatch.EndChunkIndex, dbBatch.Hash, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update batch_hash for chunks (batch hash: %s, start chunk: %d, end chunk: %d): %w", dbBatch.Hash, dbBatch.StartChunkIndex, dbBatch.EndChunkIndex, err)
|
||||
}
|
||||
|
||||
if err = f.batchORM.UpdateProvingStatus(f.ctx, dbBatch.Hash, types.ProvingTaskVerified, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update proving status for batch %s: %w", dbBatch.Hash, err)
|
||||
}
|
||||
if err = f.batchORM.UpdateRollupStatusCommitAndFinalizeTxHash(f.ctx, dbBatch.Hash, types.RollupFinalized, batch.commit.TxHash().Hex(), batch.finalize.TxHash().Hex(), dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update rollup status for batch %s: %w", dbBatch.Hash, err)
|
||||
}
|
||||
|
||||
log.Info("Inserted batch", "index", dbBatch.Index, "hash", dbBatch.Hash, "start chunk", dbBatch.StartChunkIndex, "end chunk", dbBatch.EndChunkIndex)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert batch in DB transaction: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FullRecovery) getBatchBlockRangeFromBlob(codec encoding.Codec, blobVersionedHash common.Hash, l1BlockTime uint64) ([]encoding.DABlock, error) {
|
||||
blob, err := f.beaconNodeClient.GetBlobByVersionedHashAndBlockTime(f.ctx, blobVersionedHash, l1BlockTime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get blob %s: %w", blobVersionedHash.Hex(), err)
|
||||
}
|
||||
if blob == nil {
|
||||
return nil, fmt.Errorf("blob %s not found", blobVersionedHash.Hex())
|
||||
}
|
||||
|
||||
blobPayload, err := codec.DecodeBlob(blob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("blob %s decode error: %w", blobVersionedHash.Hex(), err)
|
||||
}
|
||||
|
||||
blocks := blobPayload.Blocks()
|
||||
if len(blocks) == 0 {
|
||||
return nil, fmt.Errorf("empty blocks in blob %s", blobVersionedHash.Hex())
|
||||
}
|
||||
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
type batchEvents struct {
|
||||
commit *l1.CommitBatchEvent
|
||||
finalize *l1.FinalizeBatchEvent
|
||||
}
|
||||
|
||||
func newBatchEvents(commit *l1.CommitBatchEvent, finalize *l1.FinalizeBatchEvent) *batchEvents {
|
||||
if commit.BatchIndex().Uint64() > finalize.BatchIndex().Uint64() {
|
||||
panic(fmt.Sprintf("commit and finalize batch index mismatch: %d != %d", commit.BatchIndex().Uint64(), finalize.BatchIndex().Uint64()))
|
||||
}
|
||||
|
||||
return &batchEvents{
|
||||
commit: commit,
|
||||
finalize: finalize,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *batchEvents) CompareTo(other *batchEvents) int {
|
||||
return e.commit.BatchIndex().Cmp(other.commit.BatchIndex())
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
@@ -20,7 +22,6 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/params"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
"scroll-tech/common/types/message"
|
||||
@@ -289,6 +290,12 @@ func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte,
|
||||
log.Info("Validium importGenesis", "calldata", common.Bytes2Hex(calldata))
|
||||
} else {
|
||||
// rollup mode: pass batchHeader and stateRoot
|
||||
|
||||
// Check state root is not zero
|
||||
if stateRoot == (common.Hash{}) {
|
||||
return fmt.Errorf("state root is zero")
|
||||
}
|
||||
|
||||
calldata, packErr = r.l1RollupABI.Pack("importGenesisBatch", batchHeader, stateRoot)
|
||||
if packErr != nil {
|
||||
return fmt.Errorf("failed to pack rollup importGenesisBatch with batch header: %v and state root: %v. error: %v", common.Bytes2Hex(batchHeader), stateRoot, packErr)
|
||||
@@ -338,8 +345,16 @@ func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte,
|
||||
// - backlogCount > r.cfg.BatchSubmission.BacklogMax -> forceSubmit
|
||||
// - we have at least minBatches AND price hits a desired target price
|
||||
func (r *Layer2Relayer) ProcessPendingBatches() {
|
||||
// Get effective batch limits based on whether validium mode is enabled.
|
||||
minBatches, maxBatches := r.getEffectiveBatchLimits()
|
||||
// First, get the backlog count to determine batch submission strategy
|
||||
backlogCount, err := r.batchOrm.GetFailedAndPendingBatchesCount(r.ctx)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch pending L2 batches count", "err", err)
|
||||
return
|
||||
}
|
||||
r.metrics.rollupL2RelayerBacklogCounts.Set(float64(backlogCount))
|
||||
|
||||
// Get effective batch limits based on validium mode and backlog size.
|
||||
minBatches, maxBatches := r.getEffectiveBatchLimits(backlogCount)
|
||||
|
||||
// get pending batches from database in ascending order by their index.
|
||||
dbBatches, err := r.batchOrm.GetFailedAndPendingBatches(r.ctx, maxBatches)
|
||||
@@ -353,15 +368,6 @@ func (r *Layer2Relayer) ProcessPendingBatches() {
|
||||
return
|
||||
}
|
||||
|
||||
// if backlog outgrow max size, force‐submit enough oldest batches
|
||||
backlogCount, err := r.batchOrm.GetFailedAndPendingBatchesCount(r.ctx)
|
||||
r.metrics.rollupL2RelayerBacklogCounts.Set(float64(backlogCount))
|
||||
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch pending L2 batches", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
var forceSubmit bool
|
||||
|
||||
startChunk, err := r.chunkOrm.GetChunkByIndex(r.ctx, dbBatches[0].StartChunkIndex)
|
||||
@@ -387,8 +393,6 @@ func (r *Layer2Relayer) ProcessPendingBatches() {
|
||||
// return if not hitting target price
|
||||
if skip {
|
||||
log.Debug("Skipping batch submission", "first batch index", dbBatches[0].Index, "backlog count", backlogCount, "reason", err)
|
||||
log.Debug("first batch index", dbBatches[0].Index)
|
||||
log.Debug("backlog count", backlogCount)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
@@ -501,6 +505,11 @@ func (r *Layer2Relayer) ProcessPendingBatches() {
|
||||
log.Error("failed to construct normal payload", "codecVersion", codecVersion, "start index", firstBatch.Index, "end index", lastBatch.Index, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = r.sanityChecksCommitBatchCodecV7CalldataAndBlobs(calldata, blobs); err != nil {
|
||||
log.Error("Sanity check failed for calldata and blobs", "codecVersion", codecVersion, "start index", firstBatch.Index, "end index", lastBatch.Index, "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Error("unsupported codec version in ProcessPendingBatches", "codecVersion", codecVersion, "start index", firstBatch, "end index", lastBatch.Index)
|
||||
@@ -553,12 +562,22 @@ func (r *Layer2Relayer) ProcessPendingBatches() {
|
||||
log.Info("Sent the commitBatches tx to layer1", "batches count", len(batchesToSubmit), "start index", firstBatch.Index, "start hash", firstBatch.Hash, "end index", lastBatch.Index, "end hash", lastBatch.Hash, "tx hash", txHash.String())
|
||||
}
|
||||
|
||||
// getEffectiveBatchLimits returns the effective min and max batch limits based on whether validium mode is enabled.
|
||||
func (r *Layer2Relayer) getEffectiveBatchLimits() (int, int) {
|
||||
// getEffectiveBatchLimits returns the effective min and max batch limits based on whether validium mode is enabled
|
||||
// and the current backlog size.
|
||||
// When backlogCount >= backlog_max: submit min_batches for fast inclusion at slightly higher price.
|
||||
// When backlogCount < backlog_max: submit max_batches for better cost amortization.
|
||||
func (r *Layer2Relayer) getEffectiveBatchLimits(backlogCount int64) (int, int) {
|
||||
if r.cfg.ValidiumMode {
|
||||
return 1, 1 // minBatches=1, maxBatches=1
|
||||
}
|
||||
return r.cfg.BatchSubmission.MinBatches, r.cfg.BatchSubmission.MaxBatches
|
||||
|
||||
// If backlog is at or above max, prioritize fast inclusion by submitting min_batches
|
||||
if backlogCount >= r.cfg.BatchSubmission.BacklogMax {
|
||||
return r.cfg.BatchSubmission.MinBatches, r.cfg.BatchSubmission.MinBatches
|
||||
}
|
||||
|
||||
// Otherwise, prioritize cost efficiency by trying to submit max_batches
|
||||
return r.cfg.BatchSubmission.MaxBatches, r.cfg.BatchSubmission.MaxBatches
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) contextIDFromBatches(codecVersion encoding.CodecVersion, batches []*dbBatchWithChunks) string {
|
||||
@@ -998,6 +1017,18 @@ func (r *Layer2Relayer) constructCommitBatchPayloadCodecV7(batchesToSubmit []*db
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) constructCommitBatchPayloadValidium(batch *dbBatchWithChunks) ([]byte, uint64, uint64, error) {
|
||||
// Check state root is not zero
|
||||
stateRoot := common.HexToHash(batch.Batch.StateRoot)
|
||||
if stateRoot == (common.Hash{}) {
|
||||
return nil, 0, 0, fmt.Errorf("batch %d state root is zero", batch.Batch.Index)
|
||||
}
|
||||
|
||||
// Check parent batch hash is not zero
|
||||
parentBatchHash := common.HexToHash(batch.Batch.ParentBatchHash)
|
||||
if parentBatchHash == (common.Hash{}) {
|
||||
return nil, 0, 0, fmt.Errorf("batch %d parent batch hash is zero", batch.Batch.Index)
|
||||
}
|
||||
|
||||
// Calculate metrics
|
||||
var maxBlockHeight uint64
|
||||
var totalGasUsed uint64
|
||||
@@ -1017,6 +1048,7 @@ func (r *Layer2Relayer) constructCommitBatchPayloadValidium(batch *dbBatchWithCh
|
||||
|
||||
lastChunk := batch.Chunks[len(batch.Chunks)-1]
|
||||
commitment := common.HexToHash(lastChunk.EndBlockHash)
|
||||
|
||||
version := encoding.CodecVersion(batch.Batch.CodecVersion)
|
||||
calldata, err := r.validiumABI.Pack("commitBatch", version, common.HexToHash(batch.Batch.ParentBatchHash), common.HexToHash(batch.Batch.StateRoot), common.HexToHash(batch.Batch.WithdrawRoot), commitment[:])
|
||||
if err != nil {
|
||||
@@ -1027,6 +1059,12 @@ func (r *Layer2Relayer) constructCommitBatchPayloadValidium(batch *dbBatchWithCh
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) constructFinalizeBundlePayloadCodecV7(dbBatch *orm.Batch, endChunk *orm.Chunk, aggProof *message.OpenVMBundleProof) ([]byte, error) {
|
||||
// Check state root is not zero
|
||||
stateRoot := common.HexToHash(dbBatch.StateRoot)
|
||||
if stateRoot == (common.Hash{}) {
|
||||
return nil, fmt.Errorf("batch %d state root is zero", dbBatch.Index)
|
||||
}
|
||||
|
||||
if aggProof != nil { // finalizeBundle with proof.
|
||||
calldata, packErr := r.l1RollupABI.Pack(
|
||||
"finalizeBundlePostEuclidV2",
|
||||
|
||||
487
rollup/internal/controller/relayer/l2_relayer_sanity.go
Normal file
487
rollup/internal/controller/relayer/l2_relayer_sanity.go
Normal file
@@ -0,0 +1,487 @@
|
||||
package relayer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
|
||||
|
||||
"scroll-tech/rollup/internal/orm"
|
||||
)
|
||||
|
||||
// sanityChecksCommitBatchCodecV7CalldataAndBlobs performs comprehensive validation of the constructed
|
||||
// transaction data (calldata and blobs) by parsing them and comparing against database records.
|
||||
// This ensures the constructed transaction data is correct and consistent with the database state.
|
||||
func (r *Layer2Relayer) sanityChecksCommitBatchCodecV7CalldataAndBlobs(calldata []byte, blobs []*kzg4844.Blob) error {
|
||||
if r.l1RollupABI == nil {
|
||||
return fmt.Errorf("l1RollupABI is nil: cannot parse commitBatches calldata")
|
||||
}
|
||||
calldataInfo, err := parseCommitBatchesCalldata(r.l1RollupABI, calldata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse calldata: %w", err)
|
||||
}
|
||||
|
||||
batchesToValidate, l1MessagesWithBlockNumbers, err := r.getBatchesFromCalldata(calldataInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get batches from database: %w", err)
|
||||
}
|
||||
|
||||
if err := r.validateCalldataAndBlobsAgainstDatabase(calldataInfo, blobs, batchesToValidate, l1MessagesWithBlockNumbers); err != nil {
|
||||
return fmt.Errorf("calldata and blobs validation failed: %w", err)
|
||||
}
|
||||
|
||||
if err := r.validateDatabaseConsistency(batchesToValidate); err != nil {
|
||||
return fmt.Errorf("database consistency validation failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalldataInfo holds parsed information from commitBatches calldata
|
||||
type CalldataInfo struct {
|
||||
Version uint8
|
||||
ParentBatchHash common.Hash
|
||||
LastBatchHash common.Hash
|
||||
}
|
||||
|
||||
// parseCommitBatchesCalldata parses the commitBatches calldata and extracts key information
|
||||
func parseCommitBatchesCalldata(abi *abi.ABI, calldata []byte) (*CalldataInfo, error) {
|
||||
method := abi.Methods["commitBatches"]
|
||||
decoded, err := method.Inputs.Unpack(calldata[4:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unpack commitBatches calldata: %w", err)
|
||||
}
|
||||
|
||||
if len(decoded) != 3 {
|
||||
return nil, fmt.Errorf("unexpected number of decoded parameters: got %d, want 3", len(decoded))
|
||||
}
|
||||
|
||||
version, ok := decoded[0].(uint8)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to type assert version to uint8")
|
||||
}
|
||||
|
||||
parentBatchHashB, ok := decoded[1].([32]uint8)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to type assert parentBatchHash to [32]uint8")
|
||||
}
|
||||
parentBatchHash := common.BytesToHash(parentBatchHashB[:])
|
||||
|
||||
lastBatchHashB, ok := decoded[2].([32]uint8)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to type assert lastBatchHash to [32]uint8")
|
||||
}
|
||||
lastBatchHash := common.BytesToHash(lastBatchHashB[:])
|
||||
|
||||
return &CalldataInfo{
|
||||
Version: version,
|
||||
ParentBatchHash: parentBatchHash,
|
||||
LastBatchHash: lastBatchHash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getBatchesFromCalldata retrieves the relevant batches from database based on calldata information
|
||||
func (r *Layer2Relayer) getBatchesFromCalldata(info *CalldataInfo) ([]*dbBatchWithChunks, map[uint64][]*types.TransactionData, error) {
|
||||
// Get the parent batch to determine the starting point
|
||||
parentBatch, err := r.batchOrm.GetBatchByHash(r.ctx, info.ParentBatchHash.Hex())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get parent batch by hash %s: %w", info.ParentBatchHash.Hex(), err)
|
||||
}
|
||||
|
||||
// Get the last batch to determine the ending point
|
||||
lastBatch, err := r.batchOrm.GetBatchByHash(r.ctx, info.LastBatchHash.Hex())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get last batch by hash %s: %w", info.LastBatchHash.Hex(), err)
|
||||
}
|
||||
|
||||
// Get all batches in the range (parent+1 to last)
|
||||
firstBatchIndex := parentBatch.Index + 1
|
||||
lastBatchIndex := lastBatch.Index
|
||||
|
||||
// Check if the range is valid
|
||||
if firstBatchIndex > lastBatchIndex {
|
||||
return nil, nil, fmt.Errorf("no batches found in range: first index %d, last index %d", firstBatchIndex, lastBatchIndex)
|
||||
}
|
||||
|
||||
var batchesToValidate []*dbBatchWithChunks
|
||||
l1MessagesWithBlockNumbers := make(map[uint64][]*types.TransactionData)
|
||||
for batchIndex := firstBatchIndex; batchIndex <= lastBatchIndex; batchIndex++ {
|
||||
dbBatch, err := r.batchOrm.GetBatchByIndex(r.ctx, batchIndex)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get batch by index %d: %w", batchIndex, err)
|
||||
}
|
||||
|
||||
// Get chunks for this batch
|
||||
dbChunks, err := r.chunkOrm.GetChunksInRange(r.ctx, dbBatch.StartChunkIndex, dbBatch.EndChunkIndex)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get chunks for batch %d: %w", batchIndex, err)
|
||||
}
|
||||
|
||||
batchesToValidate = append(batchesToValidate, &dbBatchWithChunks{
|
||||
Batch: dbBatch,
|
||||
Chunks: dbChunks,
|
||||
})
|
||||
|
||||
// If there are L1 messages in this batch, retrieve L1 messages with block numbers
|
||||
for _, chunk := range dbChunks {
|
||||
if chunk.TotalL1MessagesPoppedInChunk > 0 {
|
||||
blockWithL1Messages, err := r.l2BlockOrm.GetL2BlocksInRange(r.ctx, chunk.StartBlockNumber, chunk.EndBlockNumber)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get L2 blocks for chunk %d: %w", chunk.Index, err)
|
||||
}
|
||||
var l1MessagesCount uint64
|
||||
for _, block := range blockWithL1Messages {
|
||||
bn := block.Header.Number.Uint64()
|
||||
seenL2 := false
|
||||
for _, tx := range block.Transactions {
|
||||
if tx.Type == types.L1MessageTxType {
|
||||
if seenL2 {
|
||||
// Invariant violated: found an L1 message after an L2 transaction in the same block.
|
||||
return nil, nil, fmt.Errorf("L1 message after L2 transaction in block %d", bn)
|
||||
}
|
||||
l1MessagesWithBlockNumbers[bn] = append(l1MessagesWithBlockNumbers[bn], tx)
|
||||
l1MessagesCount++
|
||||
} else {
|
||||
seenL2 = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if chunk.TotalL1MessagesPoppedInChunk != l1MessagesCount {
|
||||
return nil, nil, fmt.Errorf("chunk %d has inconsistent L1 messages count: expected %d, got %d", chunk.Index, chunk.TotalL1MessagesPoppedInChunk, l1MessagesCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return batchesToValidate, l1MessagesWithBlockNumbers, nil
|
||||
}
|
||||
|
||||
// validateDatabaseConsistency performs comprehensive validation of database records
|
||||
func (r *Layer2Relayer) validateDatabaseConsistency(batchesToValidate []*dbBatchWithChunks) error {
|
||||
if len(batchesToValidate) == 0 {
|
||||
return fmt.Errorf("no batches to validate")
|
||||
}
|
||||
|
||||
// Get previous chunk for continuity check
|
||||
firstChunk := batchesToValidate[0].Chunks[0]
|
||||
if firstChunk.Index == 0 {
|
||||
return fmt.Errorf("genesis chunk should not be in normal batch submission flow, chunk index: %d", firstChunk.Index)
|
||||
}
|
||||
|
||||
prevChunk, err := r.chunkOrm.GetChunkByIndex(r.ctx, firstChunk.Index-1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get previous chunk %d for continuity check: %w", firstChunk.Index-1, err)
|
||||
}
|
||||
|
||||
firstBatchCodecVersion := batchesToValidate[0].Batch.CodecVersion
|
||||
for i, batch := range batchesToValidate {
|
||||
// Validate codec version consistency
|
||||
if batch.Batch.CodecVersion != firstBatchCodecVersion {
|
||||
return fmt.Errorf("batch %d has different codec version %d, expected %d", batch.Batch.Index, batch.Batch.CodecVersion, firstBatchCodecVersion)
|
||||
}
|
||||
|
||||
// Validate individual batch
|
||||
if err := r.validateSingleBatchConsistency(batch, i, batchesToValidate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate chunks in this batch
|
||||
if err := r.validateBatchChunksConsistency(batch, prevChunk); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update prevChunk to the last chunk of this batch for next iteration
|
||||
if len(batch.Chunks) == 0 {
|
||||
return fmt.Errorf("batch %d has no chunks", batch.Batch.Index)
|
||||
}
|
||||
prevChunk = batch.Chunks[len(batch.Chunks)-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSingleBatchConsistency validates a single batch's consistency
|
||||
func (r *Layer2Relayer) validateSingleBatchConsistency(batch *dbBatchWithChunks, i int, allBatches []*dbBatchWithChunks) error {
|
||||
if batch == nil || batch.Batch == nil {
|
||||
return fmt.Errorf("batch %d is nil", i)
|
||||
}
|
||||
|
||||
if len(batch.Chunks) == 0 {
|
||||
return fmt.Errorf("batch %d has no chunks", batch.Batch.Index)
|
||||
}
|
||||
|
||||
// Validate essential batch fields
|
||||
batchHash := common.HexToHash(batch.Batch.Hash)
|
||||
if batchHash == (common.Hash{}) {
|
||||
return fmt.Errorf("batch %d hash is zero", batch.Batch.Index)
|
||||
}
|
||||
|
||||
if batch.Batch.Index == 0 {
|
||||
return fmt.Errorf("batch %d has zero index (only genesis batch should have index 0)", i)
|
||||
}
|
||||
|
||||
parentBatchHash := common.HexToHash(batch.Batch.ParentBatchHash)
|
||||
if parentBatchHash == (common.Hash{}) {
|
||||
return fmt.Errorf("batch %d parent batch hash is zero", batch.Batch.Index)
|
||||
}
|
||||
|
||||
stateRoot := common.HexToHash(batch.Batch.StateRoot)
|
||||
if stateRoot == (common.Hash{}) {
|
||||
return fmt.Errorf("batch %d state root is zero", batch.Batch.Index)
|
||||
}
|
||||
|
||||
// Check batch index continuity
|
||||
if i > 0 {
|
||||
prevBatch := allBatches[i-1]
|
||||
if batch.Batch.Index != prevBatch.Batch.Index+1 {
|
||||
return fmt.Errorf("batch index is not sequential: prev batch index %d, current batch index %d", prevBatch.Batch.Index, batch.Batch.Index)
|
||||
}
|
||||
if parentBatchHash != common.HexToHash(prevBatch.Batch.Hash) {
|
||||
return fmt.Errorf("parent batch hash does not match previous batch hash: expected %s, got %s", prevBatch.Batch.Hash, batch.Batch.ParentBatchHash)
|
||||
}
|
||||
} else {
|
||||
// For the first batch, verify continuity with parent batch from database
|
||||
parentBatch, err := r.batchOrm.GetBatchByHash(r.ctx, batch.Batch.ParentBatchHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get parent batch %s for batch %d: %w", batch.Batch.ParentBatchHash, batch.Batch.Index, err)
|
||||
}
|
||||
if batch.Batch.Index != parentBatch.Index+1 {
|
||||
return fmt.Errorf("first batch index is not sequential with parent: parent batch index %d, current batch index %d", parentBatch.Index, batch.Batch.Index)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate L1 message queue consistency
|
||||
if err := r.validateMessageQueueConsistency(batch.Batch.Index, batch.Chunks, common.HexToHash(batch.Batch.PrevL1MessageQueueHash), common.HexToHash(batch.Batch.PostL1MessageQueueHash)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBatchChunksConsistency validates chunks within a batch
|
||||
func (r *Layer2Relayer) validateBatchChunksConsistency(batch *dbBatchWithChunks, prevChunk *orm.Chunk) error {
|
||||
// Check codec version consistency between chunks and batch
|
||||
for _, chunk := range batch.Chunks {
|
||||
if chunk.CodecVersion != batch.Batch.CodecVersion {
|
||||
return fmt.Errorf("batch %d chunk %d has different codec version %d, expected %d", batch.Batch.Index, chunk.Index, chunk.CodecVersion, batch.Batch.CodecVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each chunk individually
|
||||
currentPrevChunk := prevChunk
|
||||
for j, chunk := range batch.Chunks {
|
||||
if err := r.validateSingleChunkConsistency(chunk, currentPrevChunk); err != nil {
|
||||
return fmt.Errorf("batch %d chunk %d: %w", batch.Batch.Index, j, err)
|
||||
}
|
||||
currentPrevChunk = chunk
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSingleChunkConsistency validates a single chunk
|
||||
func (r *Layer2Relayer) validateSingleChunkConsistency(chunk *orm.Chunk, prevChunk *orm.Chunk) error {
|
||||
if chunk == nil {
|
||||
return fmt.Errorf("chunk is nil")
|
||||
}
|
||||
|
||||
chunkHash := common.HexToHash(chunk.Hash)
|
||||
if chunkHash == (common.Hash{}) {
|
||||
return fmt.Errorf("chunk %d hash is zero", chunk.Index)
|
||||
}
|
||||
|
||||
// Check chunk index continuity
|
||||
if chunk.Index != prevChunk.Index+1 {
|
||||
return fmt.Errorf("chunk index is not sequential: prev chunk index %d, current chunk index %d", prevChunk.Index, chunk.Index)
|
||||
}
|
||||
|
||||
// Validate block range
|
||||
if chunk.StartBlockNumber == 0 && chunk.EndBlockNumber == 0 {
|
||||
return fmt.Errorf("chunk %d has zero block range", chunk.Index)
|
||||
}
|
||||
|
||||
if chunk.StartBlockNumber > chunk.EndBlockNumber {
|
||||
return fmt.Errorf("chunk %d has invalid block range: start %d > end %d", chunk.Index, chunk.StartBlockNumber, chunk.EndBlockNumber)
|
||||
}
|
||||
|
||||
// Check hash fields
|
||||
startBlockHash := common.HexToHash(chunk.StartBlockHash)
|
||||
if startBlockHash == (common.Hash{}) {
|
||||
return fmt.Errorf("chunk %d start block hash is zero", chunk.Index)
|
||||
}
|
||||
|
||||
endBlockHash := common.HexToHash(chunk.EndBlockHash)
|
||||
if endBlockHash == (common.Hash{}) {
|
||||
return fmt.Errorf("chunk %d end block hash is zero", chunk.Index)
|
||||
}
|
||||
|
||||
// Check block continuity with previous chunk
|
||||
if prevChunk.EndBlockNumber+1 != chunk.StartBlockNumber {
|
||||
return fmt.Errorf("chunk is not continuous with previous chunk %d: prev end block %d, current start block %d", prevChunk.Index, prevChunk.EndBlockNumber, chunk.StartBlockNumber)
|
||||
}
|
||||
|
||||
// Check L1 messages continuity
|
||||
expectedPoppedBefore := prevChunk.TotalL1MessagesPoppedBefore + prevChunk.TotalL1MessagesPoppedInChunk
|
||||
if chunk.TotalL1MessagesPoppedBefore != expectedPoppedBefore {
|
||||
return fmt.Errorf("L1 messages popped before is incorrect: expected %d, got %d", expectedPoppedBefore, chunk.TotalL1MessagesPoppedBefore)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateCalldataAndBlobsAgainstDatabase validates calldata and blobs against database records
|
||||
func (r *Layer2Relayer) validateCalldataAndBlobsAgainstDatabase(calldataInfo *CalldataInfo, blobs []*kzg4844.Blob, batchesToValidate []*dbBatchWithChunks, l1MessagesWithBlockNumbers map[uint64][]*types.TransactionData) error {
|
||||
// Validate blobs
|
||||
if len(blobs) == 0 {
|
||||
return fmt.Errorf("no blobs provided")
|
||||
}
|
||||
|
||||
// Validate blob count
|
||||
if len(blobs) != len(batchesToValidate) {
|
||||
return fmt.Errorf("blob count mismatch: got %d blobs, expected %d batches", len(blobs), len(batchesToValidate))
|
||||
}
|
||||
|
||||
// Get first and last batches for validation, length check is already done above
|
||||
firstBatch := batchesToValidate[0].Batch
|
||||
lastBatch := batchesToValidate[len(batchesToValidate)-1].Batch
|
||||
|
||||
// Validate codec version
|
||||
if calldataInfo.Version != uint8(firstBatch.CodecVersion) {
|
||||
return fmt.Errorf("version mismatch: calldata=%d, db=%d", calldataInfo.Version, firstBatch.CodecVersion)
|
||||
}
|
||||
|
||||
// Validate parent batch hash
|
||||
if calldataInfo.ParentBatchHash != common.HexToHash(firstBatch.ParentBatchHash) {
|
||||
return fmt.Errorf("parentBatchHash mismatch: calldata=%s, db=%s", calldataInfo.ParentBatchHash.Hex(), firstBatch.ParentBatchHash)
|
||||
}
|
||||
|
||||
// Validate last batch hash
|
||||
if calldataInfo.LastBatchHash != common.HexToHash(lastBatch.Hash) {
|
||||
return fmt.Errorf("lastBatchHash mismatch: calldata=%s, db=%s", calldataInfo.LastBatchHash.Hex(), lastBatch.Hash)
|
||||
}
|
||||
|
||||
// Get codec for blob decoding
|
||||
codec, err := encoding.CodecFromVersion(encoding.CodecVersion(firstBatch.CodecVersion))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get codec: %w", err)
|
||||
}
|
||||
|
||||
// Validate each blob against its corresponding batch
|
||||
for i, blob := range blobs {
|
||||
dbBatch := batchesToValidate[i].Batch
|
||||
if err := r.validateSingleBlobAgainstBatch(blob, dbBatch, codec, l1MessagesWithBlockNumbers); err != nil {
|
||||
return fmt.Errorf("blob validation failed for batch %d: %w", dbBatch.Index, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSingleBlobAgainstBatch validates a single blob against its batch data
|
||||
func (r *Layer2Relayer) validateSingleBlobAgainstBatch(blob *kzg4844.Blob, dbBatch *orm.Batch, codec encoding.Codec, l1MessagesWithBlockNumbers map[uint64][]*types.TransactionData) error {
|
||||
// Decode blob payload
|
||||
payload, err := codec.DecodeBlob(blob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode blob: %w", err)
|
||||
}
|
||||
|
||||
// Validate batch hash
|
||||
daBatch, err := assembleDABatchFromPayload(payload, dbBatch, codec, l1MessagesWithBlockNumbers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assemble batch from payload: %w", err)
|
||||
}
|
||||
|
||||
if daBatch.Hash() != common.HexToHash(dbBatch.Hash) {
|
||||
return fmt.Errorf("batch hash mismatch: decoded from blob=%s, db=%s", daBatch.Hash().Hex(), dbBatch.Hash)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMessageQueueConsistency validates L1 message queue hash consistency
|
||||
func (r *Layer2Relayer) validateMessageQueueConsistency(batchIndex uint64, chunks []*orm.Chunk, prevL1MsgQueueHash common.Hash, postL1MsgQueueHash common.Hash) error {
|
||||
if len(chunks) == 0 {
|
||||
return fmt.Errorf("batch %d has no chunks for message queue validation", batchIndex)
|
||||
}
|
||||
|
||||
firstChunk := chunks[0]
|
||||
lastChunk := chunks[len(chunks)-1]
|
||||
|
||||
// Calculate total L1 messages in this batch
|
||||
var totalL1MessagesInBatch uint64
|
||||
for _, chunk := range chunks {
|
||||
totalL1MessagesInBatch += chunk.TotalL1MessagesPoppedInChunk
|
||||
}
|
||||
|
||||
// If there were L1 messages processed before this batch, prev hash should not be zero
|
||||
if firstChunk.TotalL1MessagesPoppedBefore > 0 && prevL1MsgQueueHash == (common.Hash{}) {
|
||||
return fmt.Errorf("batch %d prev L1 message queue hash is zero but %d L1 messages were processed before", batchIndex, firstChunk.TotalL1MessagesPoppedBefore)
|
||||
}
|
||||
|
||||
// If there are any L1 messages processed up to this batch, post hash should not be zero
|
||||
totalL1MessagesProcessed := lastChunk.TotalL1MessagesPoppedBefore + lastChunk.TotalL1MessagesPoppedInChunk
|
||||
if totalL1MessagesProcessed > 0 && postL1MsgQueueHash == (common.Hash{}) {
|
||||
return fmt.Errorf("batch %d post L1 message queue hash is zero but %d L1 messages were processed in total", batchIndex, totalL1MessagesProcessed)
|
||||
}
|
||||
|
||||
// Prev and post queue hashes should be different if L1 messages were processed in this batch
|
||||
if totalL1MessagesInBatch > 0 && prevL1MsgQueueHash == postL1MsgQueueHash {
|
||||
return fmt.Errorf("batch %d has same prev and post L1 message queue hashes but processed %d L1 messages in this batch", batchIndex, totalL1MessagesInBatch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func assembleDABatchFromPayload(payload encoding.DABlobPayload, dbBatch *orm.Batch, codec encoding.Codec, l1MessagesWithBlockNumbers map[uint64][]*types.TransactionData) (encoding.DABatch, error) {
|
||||
blocks, err := assembleBlocksFromPayload(payload, l1MessagesWithBlockNumbers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to assemble blocks from payload batch_index=%d codec_version=%d parent_batch_hash=%s: %w", dbBatch.Index, dbBatch.CodecVersion, dbBatch.ParentBatchHash, err)
|
||||
}
|
||||
batch := &encoding.Batch{
|
||||
Index: dbBatch.Index, // The database provides only batch index, other fields are derived from blob payload
|
||||
ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), // The first batch's parent hash is verified with calldata, subsequent batches are linked via dbBatch.ParentBatchHash and verified in database consistency checks
|
||||
PrevL1MessageQueueHash: payload.PrevL1MessageQueueHash(),
|
||||
PostL1MessageQueueHash: payload.PostL1MessageQueueHash(),
|
||||
Blocks: blocks,
|
||||
Chunks: []*encoding.Chunk{ // One chunk for this batch to pass sanity checks when building DABatch
|
||||
{
|
||||
Blocks: blocks,
|
||||
PrevL1MessageQueueHash: payload.PrevL1MessageQueueHash(),
|
||||
PostL1MessageQueueHash: payload.PostL1MessageQueueHash(),
|
||||
},
|
||||
},
|
||||
}
|
||||
daBatch, err := codec.NewDABatch(batch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build DABatch batch_index=%d codec_version=%d parent_batch_hash=%s: %w", dbBatch.Index, dbBatch.CodecVersion, dbBatch.ParentBatchHash, err)
|
||||
}
|
||||
return daBatch, nil
|
||||
}
|
||||
|
||||
func assembleBlocksFromPayload(payload encoding.DABlobPayload, l1MessagesWithBlockNumbers map[uint64][]*types.TransactionData) ([]*encoding.Block, error) {
|
||||
daBlocks := payload.Blocks()
|
||||
txns := payload.Transactions()
|
||||
if len(daBlocks) != len(txns) {
|
||||
return nil, fmt.Errorf("mismatched number of blocks and transactions: %d blocks, %d transactions", len(daBlocks), len(txns))
|
||||
}
|
||||
blocks := make([]*encoding.Block, len(daBlocks))
|
||||
for i := range daBlocks {
|
||||
blocks[i] = &encoding.Block{
|
||||
Header: &types.Header{
|
||||
Number: new(big.Int).SetUint64(daBlocks[i].Number()),
|
||||
Time: daBlocks[i].Timestamp(),
|
||||
BaseFee: daBlocks[i].BaseFee(),
|
||||
GasLimit: daBlocks[i].GasLimit(),
|
||||
},
|
||||
}
|
||||
// Ensure per-block ordering: [L1 messages][L2 transactions]. Prepend L1 messages (if any), then append L2 transactions.
|
||||
if l1Messages, ok := l1MessagesWithBlockNumbers[daBlocks[i].Number()]; ok {
|
||||
blocks[i].Transactions = l1Messages
|
||||
}
|
||||
blocks[i].Transactions = append(blocks[i].Transactions, encoding.TxsToTxsData(txns[i])...)
|
||||
}
|
||||
return blocks, nil
|
||||
}
|
||||
131
rollup/internal/controller/relayer/l2_relayer_sanity_test.go
Normal file
131
rollup/internal/controller/relayer/l2_relayer_sanity_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package relayer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
|
||||
|
||||
bridgeabi "scroll-tech/rollup/abi"
|
||||
"scroll-tech/rollup/internal/orm"
|
||||
)
|
||||
|
||||
func TestAssembleDABatch(t *testing.T) {
|
||||
calldataHex := "0x9bbaa2ba0000000000000000000000000000000000000000000000000000000000000008146793a7d71663cd87ec9713f72242a3798d5e801050130a3e16efaa09fb803e58af2593dadc8b9fff75a2d27199cb97ec115bade109b8d691a512608ef180eb"
|
||||
blobsPath := filepath.Join("../../../testdata", "commit_batches_blobs.json")
|
||||
|
||||
calldata, err := hexutil.Decode(strings.TrimSpace(calldataHex))
|
||||
assert.NoErrorf(t, err, "failed to decode calldata: %s", calldataHex)
|
||||
|
||||
blobs, err := loadBlobsFromJSON(blobsPath)
|
||||
assert.NoErrorf(t, err, "failed to read blobs: %s", blobsPath)
|
||||
assert.NotEmpty(t, blobs, "no blobs provided")
|
||||
|
||||
info, err := parseCommitBatchesCalldata(bridgeabi.ScrollChainABI, calldata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
codec, err := encoding.CodecFromVersion(encoding.CodecVersion(info.Version))
|
||||
assert.NoErrorf(t, err, "failed to get codec from version %d", info.Version)
|
||||
|
||||
parentBatchHash := info.ParentBatchHash
|
||||
index := uint64(113571)
|
||||
|
||||
t.Logf("calldata parsed: version=%d parentBatchHash=%s lastBatchHash=%s blobs=%d", info.Version, info.ParentBatchHash.Hex(), info.LastBatchHash.Hex(), len(blobs))
|
||||
|
||||
fromAddr := common.HexToAddress("0x61d8d3e7f7c656493d1d76aaa1a836cedfcbc27b")
|
||||
toAddr := common.HexToAddress("0xba50f5340fb9f3bd074bd638c9be13ecb36e603d")
|
||||
l1MessagesWithBlockNumbers := map[uint64][]*types.TransactionData{
|
||||
11488527: {
|
||||
&types.TransactionData{
|
||||
Type: types.L1MessageTxType,
|
||||
Nonce: 1072515,
|
||||
Gas: 340000,
|
||||
To: &toAddr,
|
||||
Value: (*hexutil.Big)(big.NewInt(0)),
|
||||
Data: "0x8ef1332e00000000000000000000000081f3843af1fbab046b771f0d440c04ebb2b7513f000000000000000000000000cec03800074d0ac0854bf1f34153cc4c8baeeb1e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105d8300000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000084f03efa3700000000000000000000000000000000000000000000000000000000000024730000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000171bdb6e3062daaee1845ba4cb1902169feb5a9b9555a882d45637d3bd29eb83500000000000000000000000000000000000000000000000000000000",
|
||||
From: fromAddr,
|
||||
},
|
||||
},
|
||||
11488622: {
|
||||
&types.TransactionData{
|
||||
Type: types.L1MessageTxType,
|
||||
Nonce: 1072516,
|
||||
Gas: 340000,
|
||||
To: &toAddr,
|
||||
Value: (*hexutil.Big)(big.NewInt(0)),
|
||||
Data: "0x8ef1332e00000000000000000000000081f3843af1fbab046b771f0d440c04ebb2b7513f000000000000000000000000cec03800074d0ac0854bf1f34153cc4c8baeeb1e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105d8400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000084f03efa370000000000000000000000000000000000000000000000000000000000002474000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000012aeb01535c1845b689bfce22e53029ec59ec75ea20f660d7c5fcd99f55b75b6900000000000000000000000000000000000000000000000000000000",
|
||||
From: fromAddr,
|
||||
},
|
||||
},
|
||||
11489190: {
|
||||
&types.TransactionData{
|
||||
Type: types.L1MessageTxType,
|
||||
Nonce: 1072517,
|
||||
Gas: 168000,
|
||||
To: &toAddr,
|
||||
Value: (*hexutil.Big)(big.NewInt(0)),
|
||||
Data: "0x8ef1332e0000000000000000000000003b1399523f819ea4c4d3e76dddefaf4226c6ba570000000000000000000000003b1399523f819ea4c4d3e76dddefaf4226c6ba5700000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000105d8500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000",
|
||||
From: fromAddr,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, blob := range blobs {
|
||||
payload, decErr := codec.DecodeBlob(blob)
|
||||
assert.NoErrorf(t, decErr, "blob[%d] decode failed", i)
|
||||
if decErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dbBatch := &orm.Batch{
|
||||
Index: index,
|
||||
ParentBatchHash: parentBatchHash.Hex(),
|
||||
}
|
||||
|
||||
daBatch, asmErr := assembleDABatchFromPayload(payload, dbBatch, codec, l1MessagesWithBlockNumbers)
|
||||
assert.NoErrorf(t, asmErr, "blob[%d] assemble failed", i)
|
||||
if asmErr == nil {
|
||||
t.Logf("blob[%d] DABatch hash=%s", i, daBatch.Hash().Hex())
|
||||
}
|
||||
|
||||
index += 1
|
||||
parentBatchHash = daBatch.Hash()
|
||||
}
|
||||
}
|
||||
|
||||
func loadBlobsFromJSON(path string) ([]*kzg4844.Blob, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var arr []hexutil.Bytes
|
||||
if err := json.Unmarshal(raw, &arr); err != nil {
|
||||
return nil, fmt.Errorf("invalid JSON; expect [\"0x...\"] array: %w", err)
|
||||
}
|
||||
|
||||
out := make([]*kzg4844.Blob, 0, len(arr))
|
||||
var empty kzg4844.Blob
|
||||
want := len(empty)
|
||||
|
||||
for i, b := range arr {
|
||||
if len(b) != want {
|
||||
return nil, fmt.Errorf("blob[%d] length mismatch: got %d, want %d", i, len(b), want)
|
||||
}
|
||||
blob := new(kzg4844.Blob)
|
||||
copy(blob[:], b)
|
||||
out = append(out, blob)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -70,15 +70,18 @@ func testL2RelayerProcessPendingBatches(t *testing.T) {
|
||||
_, err = chunkOrm.InsertChunk(context.Background(), chunk2, encoding.CodecV7, rutils.ChunkMetrics{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
batchOrm := orm.NewBatch(db)
|
||||
genesisBatch, err := batchOrm.GetBatchByIndex(context.Background(), 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
batch := &encoding.Batch{
|
||||
Index: 1,
|
||||
TotalL1MessagePoppedBefore: 0,
|
||||
ParentBatchHash: common.Hash{},
|
||||
ParentBatchHash: common.HexToHash(genesisBatch.Hash),
|
||||
Chunks: []*encoding.Chunk{chunk1, chunk2},
|
||||
Blocks: []*encoding.Block{block1, block2},
|
||||
}
|
||||
|
||||
batchOrm := orm.NewBatch(db)
|
||||
dbBatch, err := batchOrm.InsertBatch(context.Background(), batch, encoding.CodecV7, rutils.BatchMetrics{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ func setupEnv(t *testing.T) {
|
||||
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint, err = testApps.GetPoSL1EndPoint()
|
||||
assert.NoError(t, err)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.WriteEndpoints = []string{cfg.L2Config.RelayerConfig.SenderConfig.Endpoint, cfg.L2Config.RelayerConfig.SenderConfig.Endpoint}
|
||||
cfg.L1Config.RelayerConfig.SenderConfig.Endpoint, err = testApps.GetL2GethEndPoint()
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -81,6 +82,7 @@ func setupEnv(t *testing.T) {
|
||||
block1 = &encoding.Block{}
|
||||
err = json.Unmarshal(templateBlockTrace1, block1)
|
||||
assert.NoError(t, err)
|
||||
block1.Header.Number = big.NewInt(1)
|
||||
chunk1 = &encoding.Chunk{Blocks: []*encoding.Block{block1}}
|
||||
codec, err := encoding.CodecFromVersion(encoding.CodecV0)
|
||||
assert.NoError(t, err)
|
||||
@@ -94,6 +96,7 @@ func setupEnv(t *testing.T) {
|
||||
block2 = &encoding.Block{}
|
||||
err = json.Unmarshal(templateBlockTrace2, block2)
|
||||
assert.NoError(t, err)
|
||||
block2.Header.Number = big.NewInt(2)
|
||||
chunk2 = &encoding.Chunk{Blocks: []*encoding.Block{block2}}
|
||||
daChunk2, err := codec.NewDAChunk(chunk2, chunk1.NumL1Messages(0))
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
@@ -63,11 +64,12 @@ type FeeData struct {
|
||||
gasLimit uint64
|
||||
}
|
||||
|
||||
// Sender Transaction sender to send transaction to l1/l2 geth
|
||||
// Sender Transaction sender to send transaction to l1/l2
|
||||
type Sender struct {
|
||||
config *config.SenderConfig
|
||||
gethClient *gethclient.Client
|
||||
client *ethclient.Client // The client to retrieve on chain data or send transaction.
|
||||
client *ethclient.Client // The client to retrieve on chain data (read-only)
|
||||
writeClients []*ethclient.Client // The clients to send transactions to (write operations)
|
||||
transactionSigner *TransactionSigner
|
||||
chainID *big.Int // The chain id of the endpoint
|
||||
ctx context.Context
|
||||
@@ -90,9 +92,10 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c
|
||||
return nil, fmt.Errorf("invalid params, EscalateMultipleNum; %v, EscalateMultipleDen: %v", config.EscalateMultipleNum, config.EscalateMultipleDen)
|
||||
}
|
||||
|
||||
// Initialize read client
|
||||
rpcClient, err := rpc.Dial(config.Endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial eth client, err: %w", err)
|
||||
return nil, fmt.Errorf("failed to dial read client, err: %w", err)
|
||||
}
|
||||
|
||||
client := ethclient.NewClient(rpcClient)
|
||||
@@ -105,18 +108,42 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c
|
||||
return nil, fmt.Errorf("failed to create transaction signer, err: %w", err)
|
||||
}
|
||||
|
||||
// Set pending nonce
|
||||
nonce, err := client.PendingNonceAt(ctx, transactionSigner.GetAddr())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get pending nonce for address %s, err: %w", transactionSigner.GetAddr(), err)
|
||||
}
|
||||
transactionSigner.SetNonce(nonce)
|
||||
// Initialize write clients
|
||||
var writeClients []*ethclient.Client
|
||||
if len(config.WriteEndpoints) > 0 {
|
||||
// Use specified write endpoints
|
||||
for i, endpoint := range config.WriteEndpoints {
|
||||
writeRpcClient, err := rpc.Dial(endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial write client %d (endpoint: %s), err: %w", i, endpoint, err)
|
||||
}
|
||||
writeClient := ethclient.NewClient(writeRpcClient)
|
||||
|
||||
// Verify the write client is connected to the same chain
|
||||
writeChainID, err := writeClient.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get chain ID from write client %d (endpoint: %s), err: %w", i, endpoint, err)
|
||||
}
|
||||
if writeChainID.Cmp(chainID) != 0 {
|
||||
return nil, fmt.Errorf("write client %d (endpoint: %s) has different chain ID %s, expected %s", i, endpoint, writeChainID.String(), chainID.String())
|
||||
}
|
||||
|
||||
writeClients = append(writeClients, writeClient)
|
||||
}
|
||||
log.Info("initialized sender with multiple write clients", "service", service, "name", name, "readEndpoint", config.Endpoint, "writeEndpoints", config.WriteEndpoints)
|
||||
} else {
|
||||
// Use read client for writing (backward compatibility)
|
||||
writeClients = append(writeClients, client)
|
||||
log.Info("initialized sender with single client", "service", service, "name", name, "endpoint", config.Endpoint)
|
||||
}
|
||||
|
||||
// Create sender instance first and then initialize nonce
|
||||
sender := &Sender{
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
gethClient: gethclient.New(rpcClient),
|
||||
client: client,
|
||||
writeClients: writeClients,
|
||||
chainID: chainID,
|
||||
transactionSigner: transactionSigner,
|
||||
db: db,
|
||||
@@ -127,8 +154,13 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c
|
||||
service: service,
|
||||
senderType: senderType,
|
||||
}
|
||||
sender.metrics = initSenderMetrics(reg)
|
||||
|
||||
// Initialize nonce using the new method
|
||||
if err := sender.resetNonce(); err != nil {
|
||||
return nil, fmt.Errorf("failed to reset nonce: %w", err)
|
||||
}
|
||||
|
||||
sender.metrics = initSenderMetrics(reg)
|
||||
go sender.loop(ctx)
|
||||
|
||||
return sender, nil
|
||||
@@ -170,6 +202,82 @@ func (s *Sender) getFeeData(target *common.Address, data []byte, sidecar *gethTy
|
||||
}
|
||||
}
|
||||
|
||||
// sendTransactionToMultipleClients sends a transaction to all write clients in parallel
|
||||
// and returns success if at least one client succeeds
|
||||
func (s *Sender) sendTransactionToMultipleClients(signedTx *gethTypes.Transaction) error {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if len(s.writeClients) == 1 {
|
||||
// Single client - use direct approach
|
||||
return s.writeClients[0].SendTransaction(ctx, signedTx)
|
||||
}
|
||||
|
||||
// Multiple clients - send in parallel
|
||||
type result struct {
|
||||
endpoint string
|
||||
err error
|
||||
}
|
||||
|
||||
resultChan := make(chan result, len(s.writeClients))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Send transaction to all write clients in parallel
|
||||
for i, client := range s.writeClients {
|
||||
wg.Add(1)
|
||||
// Determine endpoint URL for this client
|
||||
endpoint := s.config.WriteEndpoints[i]
|
||||
|
||||
go func(ep string, writeClient *ethclient.Client) {
|
||||
defer wg.Done()
|
||||
err := writeClient.SendTransaction(ctx, signedTx)
|
||||
resultChan <- result{endpoint: ep, err: err}
|
||||
}(endpoint, client)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
}()
|
||||
|
||||
// Collect results
|
||||
var errs []error
|
||||
for res := range resultChan {
|
||||
if res.err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", res.endpoint, res.err))
|
||||
log.Warn("failed to send transaction to write client",
|
||||
"endpoint", res.endpoint,
|
||||
"txHash", signedTx.Hash().Hex(),
|
||||
"nonce", signedTx.Nonce(),
|
||||
"from", s.transactionSigner.GetAddr().String(),
|
||||
"error", res.err)
|
||||
} else {
|
||||
log.Info("successfully sent transaction to write client",
|
||||
"endpoint", res.endpoint,
|
||||
"txHash", signedTx.Hash().Hex(),
|
||||
"nonce", signedTx.Nonce(),
|
||||
"from", s.transactionSigner.GetAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
// Check if at least one client succeeded
|
||||
if len(errs) < len(s.writeClients) {
|
||||
successCount := len(s.writeClients) - len(errs)
|
||||
if len(errs) > 0 {
|
||||
log.Info("transaction partially succeeded",
|
||||
"txHash", signedTx.Hash().Hex(),
|
||||
"successCount", successCount,
|
||||
"totalClients", len(s.writeClients),
|
||||
"failures", errors.Join(errs...))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// All clients failed
|
||||
return fmt.Errorf("failed to send transaction to all %d write clients: %w", len(s.writeClients), errors.Join(errs...))
|
||||
}
|
||||
|
||||
// SendTransaction send a signed L2tL1 transaction.
|
||||
func (s *Sender) SendTransaction(contextID string, target *common.Address, data []byte, blobs []*kzg4844.Blob) (common.Hash, uint64, error) {
|
||||
s.metrics.sendTransactionTotal.WithLabelValues(s.service, s.name).Inc()
|
||||
@@ -179,6 +287,12 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
|
||||
err error
|
||||
)
|
||||
|
||||
blockNumber, blockTimestamp, baseFee, blobBaseFee, err := s.getBlockNumberAndTimestampAndBaseFeeAndBlobFee(s.ctx)
|
||||
if err != nil {
|
||||
log.Error("failed to get block number and base fee", "error", err)
|
||||
return common.Hash{}, 0, fmt.Errorf("failed to get block number and base fee, err: %w", err)
|
||||
}
|
||||
|
||||
if blobs != nil {
|
||||
// check that number of pending blob-carrying txs is not too big
|
||||
if s.senderType == types.SenderTypeCommitBatch {
|
||||
@@ -195,21 +309,24 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
|
||||
if numPendingTransactions >= s.config.MaxPendingBlobTxs {
|
||||
return common.Hash{}, 0, ErrTooManyPendingBlobTxs
|
||||
}
|
||||
|
||||
}
|
||||
sidecar, err = makeSidecar(blobs)
|
||||
|
||||
if blockTimestamp < s.config.FusakaTimestamp && (s.config.FusakaTimestamp-blockTimestamp) < 180 {
|
||||
return common.Hash{}, 0, fmt.Errorf("pausing blob txs before Fusaka upgrade, eta %d seconds", s.config.FusakaTimestamp-blockTimestamp)
|
||||
}
|
||||
|
||||
version := gethTypes.BlobSidecarVersion0
|
||||
if blockTimestamp >= s.config.FusakaTimestamp {
|
||||
version = gethTypes.BlobSidecarVersion1
|
||||
}
|
||||
|
||||
sidecar, err = makeSidecar(version, blobs)
|
||||
if err != nil {
|
||||
log.Error("failed to make sidecar for blob transaction", "error", err)
|
||||
return common.Hash{}, 0, fmt.Errorf("failed to make sidecar for blob transaction, err: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
blockNumber, baseFee, blobBaseFee, err := s.getBlockNumberAndBaseFeeAndBlobFee(s.ctx)
|
||||
if err != nil {
|
||||
log.Error("failed to get block number and base fee", "error", err)
|
||||
return common.Hash{}, 0, fmt.Errorf("failed to get block number and base fee, err: %w", err)
|
||||
}
|
||||
|
||||
if feeData, err = s.getFeeData(target, data, sidecar, baseFee, blobBaseFee); err != nil {
|
||||
s.metrics.sendTransactionFailureGetFee.WithLabelValues(s.service, s.name).Inc()
|
||||
log.Error("failed to get fee data", "from", s.transactionSigner.GetAddr().String(), "nonce", s.transactionSigner.GetNonce(), "err", err)
|
||||
@@ -231,7 +348,7 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
|
||||
return common.Hash{}, 0, fmt.Errorf("failed to insert transaction, err: %w", err)
|
||||
}
|
||||
|
||||
if err := s.client.SendTransaction(s.ctx, signedTx); err != nil {
|
||||
if err := s.sendTransactionToMultipleClients(signedTx); err != nil {
|
||||
// Delete the transaction from the pending transaction table if it fails to send.
|
||||
if updateErr := s.pendingTransactionOrm.DeleteTransactionByTxHash(s.ctx, signedTx.Hash()); updateErr != nil {
|
||||
log.Error("failed to delete transaction", "tx hash", signedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", signedTx.Nonce(), "err", updateErr)
|
||||
@@ -242,7 +359,10 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
|
||||
// Check if contain nonce, and reset nonce
|
||||
// only reset nonce when it is not from resubmit
|
||||
if strings.Contains(err.Error(), "nonce too low") {
|
||||
s.resetNonce(context.Background())
|
||||
if err := s.resetNonce(); err != nil {
|
||||
log.Warn("failed to reset nonce after failed send transaction", "address", s.transactionSigner.GetAddr().String(), "err", err)
|
||||
return common.Hash{}, 0, fmt.Errorf("failed to reset nonce after failed send transaction, err: %w", err)
|
||||
}
|
||||
}
|
||||
return common.Hash{}, 0, fmt.Errorf("failed to send transaction, err: %w", err)
|
||||
}
|
||||
@@ -327,14 +447,46 @@ func (s *Sender) createTx(feeData *FeeData, target *common.Address, data []byte,
|
||||
return signedTx, nil
|
||||
}
|
||||
|
||||
// resetNonce reset nonce if send signed tx failed.
|
||||
func (s *Sender) resetNonce(ctx context.Context) {
|
||||
nonce, err := s.client.PendingNonceAt(ctx, s.transactionSigner.GetAddr())
|
||||
// initializeNonce initializes the nonce by taking the maximum of database nonce and pending nonce.
|
||||
func (s *Sender) initializeNonce() (uint64, error) {
|
||||
// Get maximum nonce from database
|
||||
dbNonce, err := s.pendingTransactionOrm.GetMaxNonceBySenderAddress(s.ctx, s.transactionSigner.GetAddr().Hex())
|
||||
if err != nil {
|
||||
log.Warn("failed to reset nonce", "address", s.transactionSigner.GetAddr().String(), "err", err)
|
||||
return
|
||||
return 0, fmt.Errorf("failed to get max nonce from database for address %s, err: %w", s.transactionSigner.GetAddr().Hex(), err)
|
||||
}
|
||||
|
||||
// Get pending nonce from the client
|
||||
pendingNonce, err := s.client.PendingNonceAt(s.ctx, s.transactionSigner.GetAddr())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get pending nonce for address %s, err: %w", s.transactionSigner.GetAddr().Hex(), err)
|
||||
}
|
||||
|
||||
// Take the maximum of pending nonce and (db nonce + 1)
|
||||
// Database stores the used nonce, so the next available nonce should be dbNonce + 1
|
||||
// When dbNonce is -1 (no records), dbNonce + 1 = 0, which is correct
|
||||
nextDbNonce := uint64(dbNonce + 1)
|
||||
var finalNonce uint64
|
||||
if pendingNonce > nextDbNonce {
|
||||
finalNonce = pendingNonce
|
||||
} else {
|
||||
finalNonce = nextDbNonce
|
||||
}
|
||||
|
||||
log.Info("nonce initialization", "address", s.transactionSigner.GetAddr().Hex(), "maxDbNonce", dbNonce, "nextDbNonce", nextDbNonce, "pendingNonce", pendingNonce, "finalNonce", finalNonce)
|
||||
|
||||
return finalNonce, nil
|
||||
}
|
||||
|
||||
// resetNonce reset nonce if send signed tx failed.
|
||||
func (s *Sender) resetNonce() error {
|
||||
nonce, err := s.initializeNonce()
|
||||
if err != nil {
|
||||
log.Error("failed to reset nonce", "address", s.transactionSigner.GetAddr().String(), "err", err)
|
||||
return fmt.Errorf("failed to reset nonce, err: %w", err)
|
||||
}
|
||||
log.Info("reset nonce", "address", s.transactionSigner.GetAddr().String(), "nonce", nonce)
|
||||
s.transactionSigner.SetNonce(nonce)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sender) createReplacingTransaction(tx *gethTypes.Transaction, baseFee, blobBaseFee uint64) (*gethTypes.Transaction, error) {
|
||||
@@ -481,6 +633,10 @@ func (s *Sender) createReplacingTransaction(tx *gethTypes.Transaction, baseFee,
|
||||
|
||||
nonce := tx.Nonce()
|
||||
s.metrics.resubmitTransactionTotal.WithLabelValues(s.service, s.name).Inc()
|
||||
|
||||
// Note: This might fail during the Fusaka upgrade, if we originally sent a V0 blob tx.
|
||||
// Normally we would need to convert it to V1 before resubmitting. However, this case is
|
||||
// unlikely and geth would still accept the V0 version, so we omit the conversion.
|
||||
signedTx, err := s.createTx(&feeData, tx.To(), tx.Data(), tx.BlobTxSidecar(), nonce)
|
||||
if err != nil {
|
||||
log.Error("failed to create signed tx (resubmit case)", "from", s.transactionSigner.GetAddr().String(), "nonce", nonce, "err", err)
|
||||
@@ -494,7 +650,7 @@ func (s *Sender) createReplacingTransaction(tx *gethTypes.Transaction, baseFee,
|
||||
func (s *Sender) checkPendingTransaction() {
|
||||
s.metrics.senderCheckPendingTransactionTotal.WithLabelValues(s.service, s.name).Inc()
|
||||
|
||||
blockNumber, baseFee, blobBaseFee, err := s.getBlockNumberAndBaseFeeAndBlobFee(s.ctx)
|
||||
blockNumber, _, baseFee, blobBaseFee, err := s.getBlockNumberAndTimestampAndBaseFeeAndBlobFee(s.ctx)
|
||||
if err != nil {
|
||||
log.Error("failed to get block number and base fee", "error", err)
|
||||
return
|
||||
@@ -611,7 +767,17 @@ func (s *Sender) checkPendingTransaction() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.client.SendTransaction(s.ctx, newSignedTx); err != nil {
|
||||
if err := s.sendTransactionToMultipleClients(newSignedTx); err != nil {
|
||||
if strings.Contains(err.Error(), "nonce too low") {
|
||||
// When we receive a 'nonce too low' error but cannot find the transaction receipt, it indicates another transaction with this nonce has already been processed, so this transaction will never be mined and should be marked as failed.
|
||||
log.Warn("nonce too low detected, marking all non-confirmed transactions with same nonce as failed", "nonce", originalTx.Nonce(), "address", s.transactionSigner.GetAddr().Hex(), "txHash", originalTx.Hash().Hex(), "newTxHash", newSignedTx.Hash().Hex(), "err", err)
|
||||
txHashes := []string{originalTx.Hash().Hex(), newSignedTx.Hash().Hex()}
|
||||
if updateErr := s.pendingTransactionOrm.UpdateTransactionStatusByTxHashes(s.ctx, txHashes, types.TxStatusConfirmedFailed); updateErr != nil {
|
||||
log.Error("failed to update transaction status", "hashes", txHashes, "err", updateErr)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// SendTransaction failed, need to rollback the previous database changes
|
||||
if rollbackErr := s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// Restore original transaction status back to pending
|
||||
@@ -662,10 +828,10 @@ func (s *Sender) getSenderMeta() *orm.SenderMeta {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sender) getBlockNumberAndBaseFeeAndBlobFee(ctx context.Context) (uint64, uint64, uint64, error) {
|
||||
func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Context) (uint64, uint64, uint64, uint64, error) {
|
||||
header, err := s.client.HeaderByNumber(ctx, big.NewInt(rpc.PendingBlockNumber.Int64()))
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to get header by number, err: %w", err)
|
||||
return 0, 0, 0, 0, fmt.Errorf("failed to get header by number, err: %w", err)
|
||||
}
|
||||
|
||||
var baseFee uint64
|
||||
@@ -678,10 +844,10 @@ func (s *Sender) getBlockNumberAndBaseFeeAndBlobFee(ctx context.Context) (uint64
|
||||
blobBaseFee = misc.CalcBlobFee(*excess).Uint64()
|
||||
}
|
||||
// header.Number.Uint64() returns the pendingBlockNumber, so we minus 1 to get the latestBlockNumber.
|
||||
return header.Number.Uint64() - 1, baseFee, blobBaseFee, nil
|
||||
return header.Number.Uint64() - 1, header.Time, baseFee, blobBaseFee, nil
|
||||
}
|
||||
|
||||
func makeSidecar(blobsInput []*kzg4844.Blob) (*gethTypes.BlobTxSidecar, error) {
|
||||
func makeSidecar(version byte, blobsInput []*kzg4844.Blob) (*gethTypes.BlobTxSidecar, error) {
|
||||
if len(blobsInput) == 0 {
|
||||
return nil, errors.New("blobsInput is empty")
|
||||
}
|
||||
@@ -698,23 +864,33 @@ func makeSidecar(blobsInput []*kzg4844.Blob) (*gethTypes.BlobTxSidecar, error) {
|
||||
var proofs []kzg4844.Proof
|
||||
|
||||
for i := range blobs {
|
||||
// Calculate commitment
|
||||
c, err := kzg4844.BlobToCommitment(&blobs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get blob commitment, err: %w", err)
|
||||
}
|
||||
|
||||
p, err := kzg4844.ComputeBlobProof(&blobs[i], c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute blob proof, err: %w", err)
|
||||
}
|
||||
|
||||
commitments = append(commitments, c)
|
||||
proofs = append(proofs, p)
|
||||
|
||||
// Calculate proof
|
||||
switch version {
|
||||
case gethTypes.BlobSidecarVersion0:
|
||||
p, err := kzg4844.ComputeBlobProof(&blobs[i], c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute v0 blob proof, err: %w", err)
|
||||
}
|
||||
proofs = append(proofs, p)
|
||||
|
||||
case gethTypes.BlobSidecarVersion1:
|
||||
ps, err := kzg4844.ComputeCellProofs(&blobs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute v1 blob cell proofs, err: %w", err)
|
||||
}
|
||||
proofs = append(proofs, ps...)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported blob sidecar version: %d", version)
|
||||
}
|
||||
}
|
||||
|
||||
return &gethTypes.BlobTxSidecar{
|
||||
Blobs: blobs,
|
||||
Commitments: commitments,
|
||||
Proofs: proofs,
|
||||
}, nil
|
||||
return gethTypes.NewBlobTxSidecar(version, blobs, commitments, proofs), nil
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ func testAccessListTransactionGasLimit(t *testing.T) {
|
||||
|
||||
var sidecar *gethTypes.BlobTxSidecar
|
||||
if txBlob[i] != nil {
|
||||
sidecar, err = makeSidecar([]*kzg4844.Blob{txBlob[i]})
|
||||
sidecar, err = makeSidecar(gethTypes.BlobSidecarVersion0, []*kzg4844.Blob{txBlob[i]})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ func testResubmitNonZeroGasPriceTransaction(t *testing.T) {
|
||||
}
|
||||
var sidecar *gethTypes.BlobTxSidecar
|
||||
if txBlob[i] != nil {
|
||||
sidecar, err = makeSidecar([]*kzg4844.Blob{txBlob[i]})
|
||||
sidecar, err = makeSidecar(gethTypes.BlobSidecarVersion0, []*kzg4844.Blob{txBlob[i]})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
tx, err := s.createTx(feeData, &common.Address{}, nil, sidecar, s.transactionSigner.GetNonce())
|
||||
@@ -467,7 +467,7 @@ func testResubmitBlobTransactionWithRisingBaseFeeAndBlobBaseFee(t *testing.T) {
|
||||
})
|
||||
defer patchGuard.Reset()
|
||||
|
||||
sidecar, err := makeSidecar(randBlobs(1))
|
||||
sidecar, err := makeSidecar(gethTypes.BlobSidecarVersion0, randBlobs(1))
|
||||
assert.NoError(t, err)
|
||||
tx := gethTypes.NewTx(&gethTypes.BlobTx{
|
||||
ChainID: uint256.MustFromBig(s.chainID),
|
||||
@@ -799,7 +799,7 @@ func testBlobTransactionWithBlobhashOpContractCall(t *testing.T) {
|
||||
assert.NoError(t, migrate.ResetDB(sqlDB))
|
||||
|
||||
blobs := randBlobs(1)
|
||||
sideCar, err := makeSidecar(blobs)
|
||||
sideCar, err := makeSidecar(gethTypes.BlobSidecarVersion0, blobs)
|
||||
assert.NoError(t, err)
|
||||
versionedHash := sideCar.BlobHashes()[0]
|
||||
blsModulo, ok := new(big.Int).SetString("52435875175126190479447740508185965837690552500527637822603658699938581184513", 10)
|
||||
|
||||
@@ -36,7 +36,7 @@ func testBatchProposerLimitsCodecV7(t *testing.T) {
|
||||
name: "Timeout",
|
||||
batchTimeoutSec: 0,
|
||||
expectedBatchesLen: 1,
|
||||
expectedChunksInFirstBatch: 2,
|
||||
expectedChunksInFirstBatch: 1,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -72,8 +72,7 @@ func testBatchProposerLimitsCodecV7(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
|
||||
MaxBlockNumPerChunk: 1,
|
||||
MaxL2GasPerChunk: 20000000,
|
||||
MaxL2GasPerChunk: math.MaxUint64,
|
||||
ChunkTimeoutSec: 300,
|
||||
MaxUncompressedBatchBytesSize: math.MaxUint64,
|
||||
}, encoding.CodecV7, ¶ms.ChainConfig{
|
||||
@@ -154,7 +153,6 @@ func testBatchProposerBlobSizeLimitCodecV7(t *testing.T) {
|
||||
chainConfig := ¶ms.ChainConfig{LondonBlock: big.NewInt(0), BernoulliBlock: big.NewInt(0), CurieBlock: big.NewInt(0), DarwinTime: new(uint64), DarwinV2Time: new(uint64), EuclidTime: new(uint64), EuclidV2Time: new(uint64)}
|
||||
|
||||
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
|
||||
MaxBlockNumPerChunk: math.MaxUint64,
|
||||
MaxL2GasPerChunk: math.MaxUint64,
|
||||
ChunkTimeoutSec: 0,
|
||||
MaxUncompressedBatchBytesSize: math.MaxUint64,
|
||||
@@ -227,7 +225,6 @@ func testBatchProposerMaxChunkNumPerBatchLimitCodecV7(t *testing.T) {
|
||||
chainConfig := ¶ms.ChainConfig{LondonBlock: big.NewInt(0), BernoulliBlock: big.NewInt(0), CurieBlock: big.NewInt(0), DarwinTime: new(uint64), DarwinV2Time: new(uint64), EuclidTime: new(uint64), EuclidV2Time: new(uint64)}
|
||||
|
||||
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
|
||||
MaxBlockNumPerChunk: math.MaxUint64,
|
||||
MaxL2GasPerChunk: math.MaxUint64,
|
||||
ChunkTimeoutSec: 0,
|
||||
MaxUncompressedBatchBytesSize: math.MaxUint64,
|
||||
@@ -309,15 +306,14 @@ func testBatchProposerUncompressedBatchBytesLimitCodecV8(t *testing.T) {
|
||||
|
||||
// Create chunk proposer with no uncompressed batch bytes limit for chunks
|
||||
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
|
||||
MaxBlockNumPerChunk: 1, // One block per chunk
|
||||
MaxL2GasPerChunk: math.MaxUint64,
|
||||
MaxL2GasPerChunk: 1200000, // One block per chunk via gas limit
|
||||
ChunkTimeoutSec: math.MaxUint32,
|
||||
MaxUncompressedBatchBytesSize: math.MaxUint64,
|
||||
}, encoding.CodecV8, chainConfig, db, nil)
|
||||
|
||||
// Insert 2 blocks with large calldata and create 2 chunks
|
||||
l2BlockOrm := orm.NewL2Block(db)
|
||||
for i := uint64(1); i <= 2; i++ {
|
||||
for i := uint64(1); i <= 3; i++ {
|
||||
blockCopy := *block
|
||||
blockCopy.Header = &gethTypes.Header{}
|
||||
*blockCopy.Header = *block.Header
|
||||
@@ -326,7 +322,9 @@ func testBatchProposerUncompressedBatchBytesLimitCodecV8(t *testing.T) {
|
||||
err := l2BlockOrm.InsertL2Blocks(context.Background(), []*encoding.Block{&blockCopy})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cp.TryProposeChunk() // Each call creates one chunk with one block
|
||||
cp.TryProposeChunk() // Each chunk will contain 1 block (~3KiB)
|
||||
// We create 2 chunks here, as we have 3 blocks and reach the gas limit for the 1st chunk with the 2nd block
|
||||
// and the 2nd chunk with the 3rd block.
|
||||
}
|
||||
|
||||
// Create batch proposer with 4KiB uncompressed batch bytes limit
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user