Compare commits

..

29 Commits

Author SHA1 Message Date
Ho
7de388ef1a [Fix] Accept proof submission even it has been timeout (#1764) 2025-12-12 12:18:34 +09:00
Morty
27dd62eac3 feat(rollup-relayer): add blob fee tolerance (#1773) 2025-12-03 21:49:17 +08:00
Ho
22479a7952 [Feat] Galileo v2 (#1771)
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2025-12-02 11:04:57 +01:00
Péter Garamvölgyi
690bc01c41 feat: force commit batches at hardfork boundary (#1768) 2025-11-30 20:36:53 +01:00
Péter Garamvölgyi
e75d6c16a9 feat: propose chunk at hardfork boundary (#1767) 2025-11-28 17:21:51 +01:00
Péter Garamvölgyi
752e4e1117 fix: Fix blob fee overflow on rollup-relayer and gas-oracle (#1772) 2025-11-28 15:44:37 +01:00
georgehao
2ecc42e2f5 Return when total < request page (#1766) 2025-11-25 23:36:40 +08:00
georgehao
de72e2dccb remove unused check (#1765) 2025-11-25 22:12:16 +08:00
georgehao
edb51236e2 bump version (#1763) 2025-11-25 21:00:57 +08:00
georgehao
15a23478d1 fix bridge history GetL2UnclaimedWithdrawalsByAddress (#1760)
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2025-11-25 20:59:31 +08:00
Morty
9100a0bd4a fix: ci lint (#1762) 2025-11-25 17:45:05 +08:00
Morty
0ede0cd41f feat(blob-uploader): upload blob once proposed (#1759)
Co-authored-by: yiweichi <yiweichi@users.noreply.github.com>
2025-11-25 14:07:08 +08:00
Ho
9dceae1ca2 [Feat] Galileo forking (#1753)
Co-authored-by: Rohit Narurkar <rohit.narurkar@proton.me>
2025-11-24 17:37:04 +08:00
georgehao
235ba874c6 Update Galileo Dependency (#1752) 2025-11-17 18:18:48 +08:00
Zhang Zhuo
6bee33036f feat: the CLOAK privacy solution (#1737)
Co-authored-by: Ho <fan@scroll.io>
Co-authored-by: Rohit Narurkar <rohit.narurkar@proton.me>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2025-11-14 15:00:37 +01:00
Ho
1985e54ab3 [Feat] For prover 4.6.1 (#1742) 2025-10-24 16:18:40 +09:00
Péter Garamvölgyi
bfc0fdd7ce feat: support Fusaka blob type (#1746)
Co-authored-by: jonastheis <jonastheis@users.noreply.github.com>
Co-authored-by: jonastheis <4181434+jonastheis@users.noreply.github.com>
2025-10-18 08:52:29 +02:00
Jonas Theis
426c57a5fa feat(l2 relayer): enhance batch submission strategy based on backlog size (#1745) 2025-10-17 09:06:53 +08:00
Péter Garamvölgyi
b7fdf48c30 ci: Do not push latest tag on Docker images (#1744) 2025-09-30 08:40:49 +02:00
johnsonjie
ad0c918944 update prover image cuda version (#1741) 2025-09-24 09:42:19 +08:00
Ho
1098876183 [Feat] Update zkvm to 0.6 (use openvm 1.4) (#1736) 2025-09-19 10:29:13 +09:00
Jonas Theis
9e520e7769 feat(tx sender): add multiple write clients for more reliable tx submission (#1740) 2025-09-19 08:56:01 +08:00
Jonas Theis
de7f6e56a9 refactor(rollup relayer): remove max_block_num_per_chunk configuration parameter (#1729)
Co-authored-by: jonastheis <jonastheis@users.noreply.github.com>
2025-09-10 14:16:13 +08:00
Ho
3b323198dc [feat] Integration e2e test tools (#1694)
Co-authored-by: georgehao <haohongfan@gmail.com>
2025-09-09 15:48:20 +09:00
Ho
c11e0283e8 [FIX] script for detecting plonky3gpu version (#1730) 2025-09-09 10:45:37 +08:00
Ho
a5a7844646 [FIX] Compatible with current prover (before 0.5.6) (#1732) 2025-09-02 19:20:02 +09:00
georgehao
7ff5b190ec bump version to v4.5.44 (#1731) 2025-09-02 09:56:59 +08:00
Ho
b297edd28d [Feat] Prover loading assets (circuits) dynamically (#1717) 2025-08-29 19:32:44 +09:00
Péter Garamvölgyi
47c85d4983 Fix unique chunk hash (#1727) 2025-08-26 14:27:24 +02:00
141 changed files with 5938 additions and 13650 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

2841
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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.7.1"
[workspace.dependencies]
scroll-zkvm-prover-euclid = { git = "https://github.com/scroll-tech/zkvm-prover", branch = "feat/0.5.1", package = "scroll-zkvm-prover" }
scroll-zkvm-verifier-euclid = { git = "https://github.com/scroll-tech/zkvm-prover", branch = "feat/0.5.1", package = "scroll-zkvm-verifier" }
scroll-zkvm-types = { git = "https://github.com/scroll-tech/zkvm-prover", branch = "feat/0.5.1" }
scroll-zkvm-prover = { git = "https://github.com/scroll-tech/zkvm-prover", tag = "v0.7.1" }
scroll-zkvm-verifier = { git = "https://github.com/scroll-tech/zkvm-prover", tag = "v0.7.1" }
scroll-zkvm-types = { git = "https://github.com/scroll-tech/zkvm-prover", tag = "v0.7.1" }
sbv-primitives = { git = "https://github.com/scroll-tech/stateless-block-verifier", branch = "chore/openvm-1.3", features = ["scroll"] }
sbv-utils = { git = "https://github.com/scroll-tech/stateless-block-verifier", branch = "chore/openvm-1.3" }
sbv-primitives = { git = "https://github.com/scroll-tech/stateless-block-verifier", tag = "scroll-v91.2", features = ["scroll", "rkyv"] }
sbv-utils = { git = "https://github.com/scroll-tech/stateless-block-verifier", tag = "scroll-v91.2" }
sbv-core = { git = "https://github.com/scroll-tech/stateless-block-verifier", tag = "scroll-v91.2", features = ["scroll"] }
metrics = "0.23.0"
metrics-util = "0.17"
@@ -30,14 +31,14 @@ 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.4.1", default-features = false, features = ["tiny-keccak"] }
# also use this to trigger "serde" feature for primitives
alloy-serde = { version = "1", default-features = false }
serde = { version = "1", default-features = false, features = ["derive"] }
serde_json = { version = "1.0" }
serde_derive = "1.0"
serde_with = "3.11.0"
serde_with = "3"
itertools = "0.14"
tiny-keccak = "2.0"
tracing = "0.1"
@@ -45,22 +46,20 @@ eyre = "0.6"
once_cell = "1.20"
base64 = "0.22"
[patch.crates-io]
revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-bytecode = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-context = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-context-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-database = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-database-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-handler = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-inspector = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-interpreter = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-precompile = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-primitives = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
revm-state = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" }
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" }
[patch.crates-io]
revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-bytecode = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-context = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-context-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-database = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-database-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-handler = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-inspector = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-interpreter = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-precompile = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-primitives = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
revm-state = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
[profile.maxperf]
inherits = "release"

View File

@@ -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.17
help: ## Display this help message
@grep -h \

View File

@@ -10,15 +10,18 @@ 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.20250825071838-cddc263e5ef6
github.com/scroll-tech/go-ethereum v1.10.14-0.20250729113104-bd8f141bb3e9
github.com/stretchr/testify v1.9.0
github.com/scroll-tech/da-codec v0.10.0
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f
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.20250729113104-bd8f141bb3e9 // It's a hotfix for the header hash incompatibility issue, pls change this with caution
// Hotfix for header hash incompatibility issue.
// PR: https://github.com/scroll-tech/go-ethereum/pull/1133/
// CAUTION: Requires careful handling. When upgrading go-ethereum, ensure this fix remains up-to-date in this branch.
replace github.com/scroll-tech/go-ethereum => github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092359-25d5bf6b817b
require (
dario.cat/mergo v1.0.0 // indirect
@@ -30,10 +33,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 +44,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 +101,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 +113,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

View File

@@ -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.20250825071838-cddc263e5ef6 h1:EiLZgoRfGgF0QzZ1jGSlo60A6W7g9L/8XLWMhwwtKJ0=
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.20250729113104-bd8f141bb3e9 h1:u371VK8eOU2Z/0SVf5KDI3eJc8msHSpJbav4do/8n38=
github.com/scroll-tech/go-ethereum v1.10.14-0.20250729113104-bd8f141bb3e9/go.mod h1:pDCZ4iGvEGmdIe4aSAGBrb7XSrKEML6/L/wEMmNxOdk=
github.com/scroll-tech/da-codec v0.10.0 h1:IPHxyTyXTWPV0Q+DZ08cod2fWkhUvrfysmj/VBpB+WU=
github.com/scroll-tech/da-codec v0.10.0/go.mod h1:MBlIP4wCXPcUDZ/Ci2B7n/2IbVU1WBo9OTFTZ5ffE0U=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092359-25d5bf6b817b h1:pMQKnroJoS/FeL1aOWkz7/u1iBHUP8PWjZstNuzoUGE=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092359-25d5bf6b817b/go.mod h1:Aa/kD1XB+OV/7rRxMQrjcPCB4b0pKyLH0gsTrtuHi38=
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=

View File

@@ -361,7 +361,6 @@ func getTxHistoryInfoFromBridgeBatchDepositMessage(message *orm.BridgeBatchDepos
func (h *HistoryLogic) getCachedTxsInfo(ctx context.Context, cacheKey string, pageNum, pageSize uint64) ([]*types.TxHistoryInfo, uint64, bool, error) {
start := int64((pageNum - 1) * pageSize)
end := start + int64(pageSize) - 1
total, err := h.redis.ZCard(ctx, cacheKey).Result()
if err != nil {
log.Error("failed to get zcard result", "error", err)
@@ -372,6 +371,10 @@ func (h *HistoryLogic) getCachedTxsInfo(ctx context.Context, cacheKey string, pa
return nil, 0, false, nil
}
if start >= total {
return nil, 0, false, nil
}
values, err := h.redis.ZRevRange(ctx, cacheKey, start, end).Result()
if err != nil {
log.Error("failed to get zrange result", "error", err)
@@ -450,5 +453,6 @@ func (h *HistoryLogic) processAndCacheTxHistoryInfo(ctx context.Context, cacheKe
log.Error("cache miss after write, expect hit", "cached key", cacheKey, "page", page, "page size", pageSize, "error", err)
return nil, 0, err
}
return pagedTxs, total, nil
}

View File

@@ -157,7 +157,7 @@ func (c *CrossMessage) GetL2UnclaimedWithdrawalsByAddress(ctx context.Context, s
db = db.Where("tx_status in (?)", []types.TxStatusType{types.TxStatusTypeSent, types.TxStatusTypeFailedRelayed, types.TxStatusTypeRelayTxReverted})
db = db.Where("sender = ?", sender)
db = db.Order("block_timestamp desc")
db = db.Limit(500)
db = db.Limit(10000)
if err := db.Find(&messages).Error; err != nil {
return nil, fmt.Errorf("failed to get L2 claimable withdrawal messages by sender address, sender: %v, error: %w", sender, err)
}

View File

@@ -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.20250625112225-a67863c65587
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f
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.20250825071838-cddc263e5ef6 // indirect
github.com/scroll-tech/da-codec v0.10.0 // 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

View File

@@ -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.20250825071838-cddc263e5ef6 h1:EiLZgoRfGgF0QzZ1jGSlo60A6W7g9L/8XLWMhwwtKJ0=
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.20250625112225-a67863c65587 h1:wG1+gb+K4iLtxAHhiAreMdIjP5x9hB64duraN2+u1QU=
github.com/scroll-tech/go-ethereum v1.10.14-0.20250625112225-a67863c65587/go.mod h1:YyfB2AyAtphlbIuDQgaxc2b9mo0zE4EBA1+qtXvzlmg=
github.com/scroll-tech/da-codec v0.10.0 h1:IPHxyTyXTWPV0Q+DZ08cod2fWkhUvrfysmj/VBpB+WU=
github.com/scroll-tech/da-codec v0.10.0/go.mod h1:MBlIP4wCXPcUDZ/Ci2B7n/2IbVU1WBo9OTFTZ5ffE0U=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f h1:j6SjP98MoWFFX9TwB1/nFYEkayqHQsrtE66Ll2C+oT0=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f/go.mod h1:Aa/kD1XB+OV/7rRxMQrjcPCB4b0pKyLH0gsTrtuHi38=
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=

View File

@@ -34,7 +34,7 @@ services:
# Sets up the genesis configuration for the go-ethereum client from a JSON file.
geth-genesis:
image: "ethereum/client-go:v1.13.14"
image: "ethereum/client-go:v1.14.0"
command: --datadir=/data/execution init /data/execution/genesis.json
volumes:
- data:/data
@@ -80,7 +80,7 @@ services:
# Runs the go-ethereum execution client with the specified, unlocked account and necessary
# APIs to allow for proof-of-stake consensus via Prysm.
geth:
image: "ethereum/client-go:v1.13.14"
image: "ethereum/client-go:v1.14.0"
command:
- --http
- --http.api=eth,net,web3

View File

@@ -1,4 +1,4 @@
FROM ethereum/client-go:v1.13.14
FROM ethereum/client-go:v1.14.0
COPY password /l1geth/
COPY genesis.json /l1geth/

View File

@@ -10,6 +10,7 @@ import (
"time"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/rpc"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/compose"
"github.com/testcontainers/testcontainers-go/modules/postgres"
@@ -166,13 +167,13 @@ func (t *TestcontainerApps) GetPoSL1EndPoint() (string, error) {
return contrainer.PortEndpoint(context.Background(), "8545/tcp", "http")
}
// GetPoSL1Client returns a ethclient by dialing running PoS L1 client
func (t *TestcontainerApps) GetPoSL1Client() (*ethclient.Client, error) {
// GetPoSL1Client returns a raw rpc client by dialing the L1 node
func (t *TestcontainerApps) GetPoSL1Client() (*rpc.Client, error) {
endpoint, err := t.GetPoSL1EndPoint()
if err != nil {
return nil, err
}
return ethclient.Dial(endpoint)
return rpc.Dial(endpoint)
}
// GetDBEndPoint returns the endpoint of the running postgres container
@@ -220,11 +221,20 @@ func (t *TestcontainerApps) GetGormDBClient() (*gorm.DB, error) {
// GetL2GethClient returns a ethclient by dialing running L2Geth
func (t *TestcontainerApps) GetL2GethClient() (*ethclient.Client, error) {
rpcCli, err := t.GetL2Client()
if err != nil {
return nil, err
}
return ethclient.NewClient(rpcCli), nil
}
// GetL2GethClient returns a rpc client by dialing running L2Geth
func (t *TestcontainerApps) GetL2Client() (*rpc.Client, error) {
endpoint, err := t.GetL2GethEndPoint()
if err != nil {
return nil, err
}
client, err := ethclient.Dial(endpoint)
client, err := rpc.Dial(endpoint)
if err != nil {
return nil, err
}

View File

@@ -3,7 +3,6 @@ package testcontainers
import (
"testing"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
)
@@ -14,7 +13,6 @@ func TestNewTestcontainerApps(t *testing.T) {
err error
endpoint string
gormDBclient *gorm.DB
ethclient *ethclient.Client
)
testApps := NewTestcontainerApps()
@@ -32,17 +30,17 @@ func TestNewTestcontainerApps(t *testing.T) {
endpoint, err = testApps.GetL2GethEndPoint()
assert.NoError(t, err)
assert.NotEmpty(t, endpoint)
ethclient, err = testApps.GetL2GethClient()
l2RawClient, err := testApps.GetL2Client()
assert.NoError(t, err)
assert.NotNil(t, ethclient)
assert.NotNil(t, l2RawClient)
assert.NoError(t, testApps.StartPoSL1Container())
endpoint, err = testApps.GetPoSL1EndPoint()
assert.NoError(t, err)
assert.NotEmpty(t, endpoint)
ethclient, err = testApps.GetPoSL1Client()
l1RawClient, err := testApps.GetPoSL1Client()
assert.NoError(t, err)
assert.NotNil(t, ethclient)
assert.NotNil(t, l1RawClient)
assert.NoError(t, testApps.StartWeb3SignerContainer(1))
endpoint, err = testApps.GetWeb3SignerEndpoint()

View File

@@ -39,10 +39,12 @@ const (
// ChunkTaskDetail is a type containing ChunkTask detail for chunk task.
type ChunkTaskDetail struct {
Version uint8 `json:"version"`
// use one of the string of "euclidv1" / "euclidv2"
ForkName string `json:"fork_name"`
BlockHashes []common.Hash `json:"block_hashes"`
PrevMsgQueueHash common.Hash `json:"prev_msg_queue_hash"`
PostMsgQueueHash common.Hash `json:"post_msg_queue_hash"`
}
// it is a hex encoded big with fixed length on 48 bytes
@@ -90,40 +92,59 @@ func (e *Byte48) UnmarshalJSON(input []byte) error {
// BatchTaskDetail is a type containing BatchTask detail.
type BatchTaskDetail struct {
Version uint8 `json:"version"`
// use one of the string of "euclidv1" / "euclidv2"
ForkName string `json:"fork_name"`
ChunkInfos []*ChunkInfo `json:"chunk_infos"`
ChunkProofs []*OpenVMChunkProof `json:"chunk_proofs"`
BatchHeader interface{} `json:"batch_header"`
BlobBytes []byte `json:"blob_bytes"`
KzgProof Byte48 `json:"kzg_proof,omitempty"`
KzgCommitment Byte48 `json:"kzg_commitment,omitempty"`
ChallengeDigest common.Hash `json:"challenge_digest,omitempty"`
ForkName string `json:"fork_name"`
ChunkProofs []*OpenVMChunkProof `json:"chunk_proofs"`
BatchHeader interface{} `json:"batch_header"`
BlobBytes []byte `json:"blob_bytes"`
KzgProof *Byte48 `json:"kzg_proof,omitempty"`
KzgCommitment *Byte48 `json:"kzg_commitment,omitempty"`
// ChallengeDigest should be a common.Hash type if it is not nil
ChallengeDigest interface{} `json:"challenge_digest,omitempty"`
}
// BundleTaskDetail consists of all the information required to describe the task to generate a proof for a bundle of batches.
type BundleTaskDetail struct {
Version uint8 `json:"version"`
// use one of the string of "euclidv1" / "euclidv2"
ForkName string `json:"fork_name"`
BatchProofs []*OpenVMBatchProof `json:"batch_proofs"`
BundleInfo *OpenVMBundleInfo `json:"bundle_info,omitempty"`
}
type RawBytes []byte
func (r RawBytes) MarshalJSON() ([]byte, error) {
if r == nil {
return []byte("null"), nil
}
// Marshal the []byte as a JSON array of numbers
rn := make([]uint16, len(r))
for i := range r {
rn[i] = uint16(r[i])
}
return json.Marshal(rn)
}
// ChunkInfo is for calculating pi_hash for chunk
type ChunkInfo struct {
ChainID uint64 `json:"chain_id"`
PrevStateRoot common.Hash `json:"prev_state_root"`
PostStateRoot common.Hash `json:"post_state_root"`
WithdrawRoot common.Hash `json:"withdraw_root"`
DataHash common.Hash `json:"data_hash"`
IsPadding bool `json:"is_padding"`
TxBytes []byte `json:"tx_bytes"`
ChainID uint64 `json:"chain_id"`
PrevStateRoot common.Hash `json:"prev_state_root"`
PostStateRoot common.Hash `json:"post_state_root"`
WithdrawRoot common.Hash `json:"withdraw_root"`
DataHash common.Hash `json:"data_hash"`
IsPadding bool `json:"is_padding"`
// TxBytes []byte `json:"tx_bytes"`
TxBytesHash common.Hash `json:"tx_data_digest"`
PrevMsgQueueHash common.Hash `json:"prev_msg_queue_hash"`
PostMsgQueueHash common.Hash `json:"post_msg_queue_hash"`
TxDataLength uint64 `json:"tx_data_length"`
InitialBlockNumber uint64 `json:"initial_block_number"`
BlockCtxs []BlockContextV2 `json:"block_ctxs"`
PrevBlockhash common.Hash `json:"prev_blockhash"`
PostBlockhash common.Hash `json:"post_blockhash"`
EncryptionKey RawBytes `json:"encryption_key"`
}
// BlockContextV2 is the block context for euclid v2
@@ -135,10 +156,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 +179,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"`
@@ -177,6 +207,7 @@ type OpenVMBatchInfo struct {
ChainID uint64 `json:"chain_id"`
PrevMsgQueueHash common.Hash `json:"prev_msg_queue_hash"`
PostMsgQueueHash common.Hash `json:"post_msg_queue_hash"`
EncryptionKey RawBytes `json:"encryption_key"`
}
// BatchProof includes the proof info that are required for batch verification and rollup.
@@ -237,6 +268,7 @@ type OpenVMBundleInfo struct {
PrevBatchHash common.Hash `json:"prev_batch_hash"`
BatchHash common.Hash `json:"batch_hash"`
MsgQueueHash common.Hash `json:"msg_queue_hash"`
EncryptionKey RawBytes `json:"encryption_key"`
}
// OpenVMBundleProof includes the proof info that are required for verification of a bundle of batch proofs.

View File

@@ -5,7 +5,7 @@ import (
"runtime/debug"
)
var tag = "v4.5.42"
var tag = "v4.7.10"
var commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {

View File

@@ -34,6 +34,19 @@ 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
mkdir -p build/bin/conf
@echo "Copying configuration files..."
@if [ -f "$(PWD)/conf/config.template.json" ]; then \
SRC="$(PWD)/conf/config.template.json"; \
else \
SRC="$(CURDIR)/conf/config.json"; \
fi; \
cp -fL "$$SRC" "$(CURDIR)/build/bin/conf/config.template.json"
@echo "Setting up releases..."
cd $(CURDIR)/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

View File

@@ -0,0 +1,72 @@
#!/bin/bash
# release version
if [ -z "${SCROLL_ZKVM_VERSION}" ]; then
echo "SCROLL_ZKVM_VERSION not set"
exit 1
fi
# default fork name from env or "galileo"
SCROLL_FORK_NAME="${SCROLL_FORK_NAME:-galileov2}"
# set ASSET_DIR by reading from config.json
CONFIG_FILE="bin/conf/config.template.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")
# skip if this verifier's fork doesn't match the target fork
if [ "$FORK_NAME" != "$SCROLL_FORK_NAME" ]; then
echo "Expect $SCROLL_FORK_NAME, skip current fork ($FORK_NAME)"
continue
fi
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/root_verifier_vk -O ${ASSET_DIR}/root_verifier_vk
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"

View File

@@ -91,11 +91,13 @@ func (c *CoordinatorApp) MockConfig(store bool) error {
ProversPerSession: 1,
Verifier: &coordinatorConfig.VerifierConfig{
MinProverVersion: "v4.4.89",
Verifiers: []coordinatorConfig.AssetConfig{{
AssetsPath: "",
ForkName: "feynman",
Verifiers: []coordinatorConfig.AssetConfig{
{
AssetsPath: "",
ForkName: "galileo",
},
},
}},
},
BatchCollectionTimeSec: 60,
ChunkCollectionTimeSec: 60,
SessionAttempts: 10,

View File

@@ -36,7 +36,7 @@ func verify(cCtx *cli.Context) error {
return fmt.Errorf("error reading file: %w", err)
}
vf, err := verifier.NewVerifier(cfg.ProverManager.Verifier)
vf, err := verifier.NewVerifier(cfg.ProverManager.Verifier, cfg.L2.ValidiumMode)
if err != nil {
return err
}

View File

@@ -10,13 +10,18 @@
"min_prover_version": "v4.4.45",
"verifiers": [
{
"assets_path": "assets",
"fork_name": "euclidV2"
},
"features": "legacy_witness:openvm_13",
"assets_path": "assets_feynman",
"fork_name": "feynman"
},
{
"assets_path": "assets",
"fork_name": "feynman"
}
"fork_name": "galileo"
},
{
"assets_path": "assets_v2",
"fork_name": "galileoV2"
}
]
}
},

View File

@@ -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.20250825071838-cddc263e5ef6
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7
github.com/scroll-tech/da-codec v0.10.0
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f
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

View File

@@ -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.20250825071838-cddc263e5ef6 h1:EiLZgoRfGgF0QzZ1jGSlo60A6W7g9L/8XLWMhwwtKJ0=
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.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.10.0 h1:IPHxyTyXTWPV0Q+DZ08cod2fWkhUvrfysmj/VBpB+WU=
github.com/scroll-tech/da-codec v0.10.0/go.mod h1:MBlIP4wCXPcUDZ/Ci2B7n/2IbVU1WBo9OTFTZ5ffE0U=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f h1:j6SjP98MoWFFX9TwB1/nFYEkayqHQsrtE66Ll2C+oT0=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f/go.mod h1:Aa/kD1XB+OV/7rRxMQrjcPCB4b0pKyLH0gsTrtuHi38=
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=

View File

@@ -36,8 +36,9 @@ type L2Endpoint struct {
// L2 loads l2geth configuration items.
type L2 struct {
// l2geth chain_id.
ChainID uint64 `json:"chain_id"`
Endpoint *L2Endpoint `json:"l2geth"`
ChainID uint64 `json:"chain_id"`
Endpoint *L2Endpoint `json:"l2geth"`
ValidiumMode bool `json:"validium_mode"`
}
// Auth provides the auth coordinator
@@ -47,19 +48,28 @@ type Auth struct {
LoginExpireDurationSec int `json:"login_expire_duration_sec"`
}
// The sequencer controlled data
type Sequencer struct {
DecryptionKey string `json:"decryption_key"`
}
// Config load configuration items.
type Config struct {
ProverManager *ProverManager `json:"prover_manager"`
DB *database.Config `json:"db"`
L2 *L2 `json:"l2"`
Auth *Auth `json:"auth"`
Sequencer *Sequencer `json:"sequencer"`
}
// 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"`
Version uint8 `json:"version,omitempty"`
ForkName string `json:"fork_name"`
Vkfile string `json:"vk_file,omitempty"`
MinProverVersion string `json:"min_prover_version,omitempty"`
Features string `json:"features,omitempty"`
}
// VerifierConfig load zk verifier config.

View File

@@ -23,7 +23,7 @@ func TestConfig(t *testing.T) {
"min_prover_version": "v4.4.45",
"verifiers": [{
"assets_path": "assets",
"fork_name": "feynman"
"fork_name": "galileo"
}]
},
"max_verifier_workers": 4
@@ -35,13 +35,17 @@ func TestConfig(t *testing.T) {
"maxIdleNum": 20
},
"l2": {
"chain_id": 111
"chain_id": 111,
"validium_mode": false
},
"auth": {
"secret": "prover secret key",
"challenge_expire_duration_sec": 3600,
"login_expire_duration_sec": 3600
}
},
"sequencer": {
"decryption_key": "sequencer decryption key"
}
}`
t.Run("Success Case", func(t *testing.T) {

View File

@@ -24,7 +24,9 @@ var (
// InitController inits Controller with database
func InitController(cfg *config.Config, chainCfg *params.ChainConfig, db *gorm.DB, reg prometheus.Registerer) {
vf, err := verifier.NewVerifier(cfg.ProverManager.Verifier)
validiumMode := cfg.L2.ValidiumMode
vf, err := verifier.NewVerifier(cfg.ProverManager.Verifier, validiumMode)
if err != nil {
panic("proof receiver new verifier failure")
}

View File

@@ -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
}

View File

@@ -93,8 +93,8 @@ func fromMessageTaskType(taskType int) int {
}
// Generate a universal task
func GenerateUniversalTask(taskType int, taskJSON, forkName string, expectedVk []byte) (bool, string, string, []byte) {
return generateUniversalTask(fromMessageTaskType(taskType), taskJSON, strings.ToLower(forkName), expectedVk)
func GenerateUniversalTask(taskType int, taskJSON, forkName string, expectedVk []byte, decryptionKey []byte) (bool, string, string, []byte) {
return generateUniversalTask(fromMessageTaskType(taskType), taskJSON, strings.ToLower(forkName), expectedVk, decryptionKey)
}
// Generate wrapped proof
@@ -140,3 +140,20 @@ func DumpVk(forkName, filePath string) error {
return nil
}
// UnivTaskCompatibilityFix calls the universal task compatibility fix function
func UniversalTaskCompatibilityFix(taskJSON string) (string, error) {
cTaskJSON := goToCString(taskJSON)
defer freeCString(cTaskJSON)
resultPtr := C.univ_task_compatibility_fix(cTaskJSON)
if resultPtr == nil {
return "", fmt.Errorf("univ_task_compatibility_fix failed")
}
// Convert result to Go string and free C memory
result := C.GoString(resultPtr)
C.release_string(resultPtr)
return result, nil
}

View File

@@ -40,7 +40,9 @@ HandlingResult gen_universal_task(
char* task,
char* fork_name,
const unsigned char* expected_vk,
size_t expected_vk_len
size_t expected_vk_len,
const unsigned char* decryption_key,
size_t decryption_key_len
);
// Release memory allocated for a HandlingResult returned by gen_universal_task
@@ -54,4 +56,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);
// Universal task compatibility fix function
char* univ_task_compatibility_fix(char* task_json);
#endif /* LIBZKP_H */

View File

@@ -14,7 +14,7 @@ import (
func InitL2geth(configJSON string) {
}
func generateUniversalTask(taskType int, taskJSON, forkName string, expectedVk []byte) (bool, string, string, []byte) {
func generateUniversalTask(taskType int, taskJSON, forkName string, expectedVk []byte, decryptionKey []byte) (bool, string, string, []byte) {
fmt.Printf("call mocked generate universal task %d, taskJson %s\n", taskType, taskJSON)
var metadata interface{}

View File

@@ -17,7 +17,7 @@ func InitL2geth(configJSON string) {
C.init_l2geth(cConfig)
}
func generateUniversalTask(taskType int, taskJSON, forkName string, expectedVk []byte) (bool, string, string, []byte) {
func generateUniversalTask(taskType int, taskJSON, forkName string, expectedVk []byte, decryptionKey []byte) (bool, string, string, []byte) {
cTask := goToCString(taskJSON)
cForkName := goToCString(forkName)
defer freeCString(cTask)
@@ -29,7 +29,13 @@ func generateUniversalTask(taskType int, taskJSON, forkName string, expectedVk [
cVk = (*C.uchar)(unsafe.Pointer(&expectedVk[0]))
}
result := C.gen_universal_task(C.int(taskType), cTask, cForkName, cVk, C.size_t(len(expectedVk)))
// Create a C array from Go slice
var cDk *C.uchar
if len(decryptionKey) > 0 {
cDk = (*C.uchar)(unsafe.Pointer(&decryptionKey[0]))
}
result := C.gen_universal_task(C.int(taskType), cTask, cForkName, cVk, C.size_t(len(expectedVk)), cDk, C.size_t(len(decryptionKey)))
defer C.release_task_result(result)
// Check if the operation was successful

View File

@@ -213,6 +213,14 @@ func (bp *BatchProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinato
return nil, ErrCoordinatorInternalFailure
}
proverTask.Metadata = metadata
if isCompatibilityFixingVersion(taskCtx.ProverVersion) {
log.Info("Apply compatibility fixing for prover", "version", taskCtx.ProverVersion)
if err := fixCompatibility(taskMsg); err != nil {
log.Error("apply compatibility failure", "err", err)
return nil, ErrCoordinatorInternalFailure
}
}
}
// Store session info.
@@ -249,36 +257,21 @@ func (bp *BatchProverTask) formatProverTask(ctx context.Context, task *orm.Prove
}
var chunkProofs []*message.OpenVMChunkProof
var chunkInfos []*message.ChunkInfo
// var chunkInfos []*message.ChunkInfo
for _, chunk := range chunks {
var proof message.OpenVMChunkProof
if encodeErr := json.Unmarshal(chunk.Proof, &proof); encodeErr != nil {
return nil, fmt.Errorf("Chunk.GetProofsByBatchHash unmarshal proof error: %w, batch hash: %v, chunk hash: %v", encodeErr, task.TaskID, chunk.Hash)
}
chunkProofs = append(chunkProofs, &proof)
chunkInfo := message.ChunkInfo{
ChainID: bp.cfg.L2.ChainID,
PrevStateRoot: common.HexToHash(chunk.ParentChunkStateRoot),
PostStateRoot: common.HexToHash(chunk.StateRoot),
WithdrawRoot: common.HexToHash(chunk.WithdrawRoot),
DataHash: common.HexToHash(chunk.Hash),
PrevMsgQueueHash: common.HexToHash(chunk.PrevL1MessageQueueHash),
PostMsgQueueHash: common.HexToHash(chunk.PostL1MessageQueueHash),
IsPadding: false,
InitialBlockNumber: proof.MetaData.ChunkInfo.InitialBlockNumber,
BlockCtxs: proof.MetaData.ChunkInfo.BlockCtxs,
TxDataLength: proof.MetaData.ChunkInfo.TxDataLength,
}
chunkInfos = append(chunkInfos, &chunkInfo)
}
taskDetail, err := bp.getBatchTaskDetail(batch, chunkInfos, chunkProofs, hardForkName)
taskDetail, err := bp.getBatchTaskDetail(batch, chunkProofs, hardForkName)
if err != nil {
return nil, fmt.Errorf("failed to get batch task detail, taskID:%s err:%w", task.TaskID, err)
}
chunkProofsBytes, err := json.Marshal(taskDetail)
taskBytesWithchunkProofs, err := json.Marshal(taskDetail)
if err != nil {
return nil, fmt.Errorf("failed to marshal chunk proofs, taskID:%s err:%w", task.TaskID, err)
}
@@ -286,7 +279,7 @@ func (bp *BatchProverTask) formatProverTask(ctx context.Context, task *orm.Prove
taskMsg := &coordinatorType.GetTaskSchema{
TaskID: task.TaskID,
TaskType: int(message.ProofTypeBatch),
TaskData: string(chunkProofsBytes),
TaskData: string(taskBytesWithchunkProofs),
HardForkName: hardForkName,
}
@@ -301,38 +294,59 @@ func (bp *BatchProverTask) recoverActiveAttempts(ctx *gin.Context, batchTask *or
}
}
func (bp *BatchProverTask) getBatchTaskDetail(dbBatch *orm.Batch, chunkInfos []*message.ChunkInfo, chunkProofs []*message.OpenVMChunkProof, hardForkName string) (*message.BatchTaskDetail, error) {
func (bp *BatchProverTask) getBatchTaskDetail(dbBatch *orm.Batch, chunkProofs []*message.OpenVMChunkProof, hardForkName string) (*message.BatchTaskDetail, error) {
// Get the version byte.
version, err := bp.version(hardForkName)
if err != nil {
return nil, fmt.Errorf("failed to decode version byte: %w", err)
}
taskDetail := &message.BatchTaskDetail{
ChunkInfos: chunkInfos,
Version: version,
ChunkProofs: chunkProofs,
ForkName: hardForkName,
}
dbBatchCodecVersion := encoding.CodecVersion(dbBatch.CodecVersion)
switch dbBatchCodecVersion {
case encoding.CodecV3, encoding.CodecV4, encoding.CodecV6, encoding.CodecV7, encoding.CodecV8:
default:
return taskDetail, nil
}
codec, err := encoding.CodecFromVersion(encoding.CodecVersion(dbBatch.CodecVersion))
if err != nil {
return nil, fmt.Errorf("failed to get codec from version %d, err: %w", dbBatch.CodecVersion, err)
}
batchHeader, decodeErr := codec.NewDABatchFromBytes(dbBatch.BatchHeader)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode batch header version %d: %w", dbBatch.CodecVersion, decodeErr)
}
taskDetail.BatchHeader = batchHeader
taskDetail.BlobBytes = dbBatch.BlobBytes
taskDetail.ChallengeDigest = common.HexToHash(dbBatch.ChallengeDigest)
// Memory layout of `BlobDataProof`: used in Codec.BlobDataProofForPointEvaluation()
// | z | y | kzg_commitment | kzg_proof |
// |---------|---------|----------------|-----------|
// | bytes32 | bytes32 | bytes48 | bytes48 |
taskDetail.KzgProof = message.Byte48{Big: hexutil.Big(*new(big.Int).SetBytes(dbBatch.BlobDataProof[112:160]))}
taskDetail.KzgCommitment = message.Byte48{Big: hexutil.Big(*new(big.Int).SetBytes(dbBatch.BlobDataProof[64:112]))}
if !bp.validiumMode() {
dbBatchCodecVersion := encoding.CodecVersion(dbBatch.CodecVersion)
switch dbBatchCodecVersion {
case 0:
log.Warn("the codec version is 0, if it is not under integration test we have encountered an error here")
return taskDetail, nil
case encoding.CodecV3, encoding.CodecV4, encoding.CodecV6, encoding.CodecV7, encoding.CodecV8, encoding.CodecV9, encoding.CodecV10:
default:
return nil, fmt.Errorf("Unsupported codec version <%d>", dbBatchCodecVersion)
}
codec, err := encoding.CodecFromVersion(encoding.CodecVersion(dbBatch.CodecVersion))
if err != nil {
return nil, fmt.Errorf("failed to get codec from version %d, err: %w", dbBatch.CodecVersion, err)
}
batchHeader, decodeErr := codec.NewDABatchFromBytes(dbBatch.BatchHeader)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode batch header version %d: %w", dbBatch.CodecVersion, decodeErr)
}
taskDetail.BatchHeader = batchHeader
taskDetail.ChallengeDigest = common.HexToHash(dbBatch.ChallengeDigest)
// Memory layout of `BlobDataProof`: used in Codec.BlobDataProofForPointEvaluation()
// | z | y | kzg_commitment | kzg_proof |
// |---------|---------|----------------|-----------|
// | bytes32 | bytes32 | bytes48 | bytes48 |
taskDetail.KzgProof = &message.Byte48{Big: hexutil.Big(*new(big.Int).SetBytes(dbBatch.BlobDataProof[112:160]))}
taskDetail.KzgCommitment = &message.Byte48{Big: hexutil.Big(*new(big.Int).SetBytes(dbBatch.BlobDataProof[64:112]))}
} else {
log.Info("Apply validium mode for batch proving task")
codec := cutils.FromVersion(version)
batchHeader, decodeErr := codec.DABatchForTaskFromBytes(dbBatch.BatchHeader)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode batch header version %d: %w", dbBatch.CodecVersion, decodeErr)
}
batchHeader.SetHash(common.HexToHash(dbBatch.Hash))
taskDetail.BatchHeader = batchHeader
}
return taskDetail, nil
}

View File

@@ -211,6 +211,14 @@ func (bp *BundleProverTask) Assign(ctx *gin.Context, getTaskParameter *coordinat
// bundle proof require snark
taskMsg.UseSnark = true
proverTask.Metadata = metadata
if isCompatibilityFixingVersion(taskCtx.ProverVersion) {
log.Info("Apply compatibility fixing for prover", "version", taskCtx.ProverVersion)
if err := fixCompatibility(taskMsg); err != nil {
log.Error("apply compatibility failure", "err", err)
return nil, ErrCoordinatorInternalFailure
}
}
}
// Store session info.
@@ -265,7 +273,14 @@ func (bp *BundleProverTask) formatProverTask(ctx context.Context, task *orm.Prov
batchProofs = append(batchProofs, &proof)
}
// Get the version byte.
version, err := bp.version(hardForkName)
if err != nil {
return nil, fmt.Errorf("failed to decode version byte: %w", err)
}
taskDetail := message.BundleTaskDetail{
Version: version,
BatchProofs: batchProofs,
ForkName: hardForkName,
}

View File

@@ -237,14 +237,21 @@ func (cp *ChunkProverTask) formatProverTask(ctx context.Context, task *orm.Prove
return nil, fmt.Errorf("failed to fetch block hashes of a chunk, chunk hash:%s err:%v", task.TaskID, dbErr)
}
// Get the version byte.
version, err := cp.version(hardForkName)
if err != nil {
return nil, fmt.Errorf("failed to decode version byte: %w", err)
}
var taskDetailBytes []byte
taskDetail := message.ChunkTaskDetail{
Version: version,
BlockHashes: blockHashes,
PrevMsgQueueHash: common.HexToHash(chunk.PrevL1MessageQueueHash),
PostMsgQueueHash: common.HexToHash(chunk.PostL1MessageQueueHash),
ForkName: hardForkName,
}
var err error
taskDetailBytes, err = json.Marshal(taskDetail)
if err != nil {
return nil, fmt.Errorf("failed to marshal block hashes hash:%s, err:%w", task.TaskID, err)

View File

@@ -1,6 +1,7 @@
package provertask
import (
"encoding/hex"
"errors"
"fmt"
"strings"
@@ -14,11 +15,13 @@ import (
"gorm.io/gorm"
"scroll-tech/common/types/message"
"scroll-tech/common/version"
"scroll-tech/coordinator/internal/config"
"scroll-tech/coordinator/internal/logic/libzkp"
"scroll-tech/coordinator/internal/orm"
coordinatorType "scroll-tech/coordinator/internal/types"
"scroll-tech/coordinator/internal/utils"
)
var (
@@ -65,6 +68,17 @@ type proverTaskContext struct {
hasAssignedTask *orm.ProverTask
}
func (b *BaseProverTask) version(hardForkName string) (uint8, error) {
return utils.Version(hardForkName, b.validiumMode())
}
// validiumMode induce different behavior in task generation:
// + skip the point_evaluation part in batch task
// + encode batch header with codec in utils instead of da-codec
func (b *BaseProverTask) validiumMode() bool {
return b.cfg.L2.ValidiumMode
}
// hardForkName get the chunk/batch/bundle hard fork name
func (b *BaseProverTask) hardForkName(ctx *gin.Context, taskCtx *proverTaskContext) (string, error) {
switch {
@@ -192,7 +206,16 @@ func (b *BaseProverTask) applyUniversal(schema *coordinatorType.GetTaskSchema) (
return nil, nil, fmt.Errorf("no expectedVk found from hardfork %s", schema.HardForkName)
}
ok, uTaskData, metadata, _ := libzkp.GenerateUniversalTask(schema.TaskType, schema.TaskData, schema.HardForkName, expectedVk)
var decryptionKey []byte
if b.cfg.L2.ValidiumMode {
var err error
decryptionKey, err = hex.DecodeString(b.cfg.Sequencer.DecryptionKey)
if err != nil {
return nil, nil, fmt.Errorf("sequencer decryption key hex-decoding failed")
}
}
ok, uTaskData, metadata, _ := libzkp.GenerateUniversalTask(schema.TaskType, schema.TaskData, schema.HardForkName, expectedVk, decryptionKey)
if !ok {
return nil, nil, fmt.Errorf("can not generate universal task, see coordinator log for the reason")
}
@@ -201,6 +224,23 @@ func (b *BaseProverTask) applyUniversal(schema *coordinatorType.GetTaskSchema) (
return schema, []byte(metadata), nil
}
const CompatibilityVersion = "4.5.43"
func isCompatibilityFixingVersion(ver string) bool {
return !version.CheckScrollRepoVersion(ver, CompatibilityVersion)
}
func fixCompatibility(schema *coordinatorType.GetTaskSchema) error {
fixedTask, err := libzkp.UniversalTaskCompatibilityFix(schema.TaskData)
if err != nil {
return err
}
schema.TaskData = fixedTask
return nil
}
func newGetTaskCounterVec(factory promauto.Factory, taskType string) *prometheus.CounterVec {
getTaskCounterInitOnce.Do(func() {
getTaskCounterVec = factory.NewCounterVec(prometheus.CounterOpts{

View File

@@ -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,13 +137,25 @@ 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"}),
}
}
// HandleZkProof handle a ZkProof submitted from a prover.
// For now only proving/verifying error will lead to setting status as skipped.
// db/unmarshal errors will not because they are errors on the business logic side.
func (m *ProofReceiverLogic) HandleZkProof(ctx *gin.Context, proofParameter coordinatorType.SubmitProofParameter) error {
func (m *ProofReceiverLogic) HandleZkProof(ctx *gin.Context, proofParameter coordinatorType.SubmitProofParameter) (rerr error) {
m.proofReceivedTotal.Inc()
pk := ctx.GetString(coordinatorType.PublicKey)
if len(pk) == 0 {
@@ -156,6 +172,18 @@ func (m *ProofReceiverLogic) HandleZkProof(ctx *gin.Context, proofParameter coor
return ErrValidatorFailureProverTaskEmpty
}
defer func() {
if rerr != nil && types.ProverProveStatus(proverTask.ProvingStatus) == types.ProverAssigned {
// trigger a last-chance closing of current task if some routine had missed it
log.Warn("last chance proof recover triggerred",
"proofID", proofParameter.TaskID,
"err", rerr,
)
m.proofRecover(ctx.Copy(), proverTask, types.ProverTaskFailureTypeUndefined, proofParameter)
}
}()
proofTime := time.Since(proverTask.CreatedAt)
proofTimeSec := uint64(proofTime.Seconds())
@@ -204,12 +232,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 {
@@ -273,6 +323,20 @@ func (m *ProofReceiverLogic) validator(ctx context.Context, proverTask *orm.Prov
}
}()
// Internally we overide the timeout failure:
// if prover task FailureType is SessionInfoFailureTimeout, the submit proof is timeout, but we still accept it
if types.ProverProveStatus(proverTask.ProvingStatus) == types.ProverProofInvalid &&
types.ProverTaskFailureType(proverTask.FailureType) == types.ProverTaskFailureTypeTimeout {
m.validateFailureProverTaskTimeout.Inc()
proverTask.ProvingStatus = int16(types.ProverAssigned)
proofTime := time.Since(proverTask.CreatedAt)
proofTimeSec := uint64(proofTime.Seconds())
log.Warn("proof submit proof have timeout", "hash", proofParameter.TaskID, "taskType", proverTask.TaskType,
"proverName", proverTask.ProverName, "proverPublicKey", pk, "proofTime", proofTimeSec)
}
// Ensure this prover is eligible to participate in the prover task.
if types.ProverProveStatus(proverTask.ProvingStatus) == types.ProverProofValid ||
types.ProverProveStatus(proverTask.ProvingStatus) == types.ProverProofInvalid {
@@ -290,9 +354,6 @@ func (m *ProofReceiverLogic) validator(ctx context.Context, proverTask *orm.Prov
return ErrValidatorFailureProverTaskCannotSubmitTwice
}
proofTime := time.Since(proverTask.CreatedAt)
proofTimeSec := uint64(proofTime.Seconds())
if proofParameter.Status != int(coordinatorType.StatusOk) {
// Temporarily replace "panic" with "pa-nic" to prevent triggering the alert based on logs.
failureMsg := strings.Replace(proofParameter.FailureMsg, "panic", "pa-nic", -1)
@@ -308,14 +369,6 @@ func (m *ProofReceiverLogic) validator(ctx context.Context, proverTask *orm.Prov
return ErrValidatorFailureProofMsgStatusNotOk
}
// if prover task FailureType is SessionInfoFailureTimeout, the submit proof is timeout, need skip it
if types.ProverTaskFailureType(proverTask.FailureType) == types.ProverTaskFailureTypeTimeout {
m.validateFailureProverTaskTimeout.Inc()
log.Info("proof submit proof have timeout, skip this submit proof", "hash", proofParameter.TaskID, "taskType", proverTask.TaskType,
"proverName", proverTask.ProverName, "proverPublicKey", pk, "proofTime", proofTimeSec)
return ErrValidatorFailureProofTimeout
}
// store the proof to prover task
if updateTaskProofErr := m.updateProverTaskProof(ctx, proverTask, proofParameter); updateTaskProofErr != nil {
log.Warn("update prover task proof failure", "hash", proofParameter.TaskID, "proverPublicKey", pk,
@@ -330,6 +383,7 @@ func (m *ProofReceiverLogic) validator(ctx context.Context, proverTask *orm.Prov
"taskType", proverTask.TaskType, "proverName", proverTask.ProverName, "proverPublicKey", pk)
return ErrValidatorFailureTaskHaveVerifiedSuccess
}
return nil
}
@@ -346,7 +400,7 @@ func (m *ProofReceiverLogic) closeProofTask(ctx context.Context, proverTask *orm
log.Info("proof close task update proof status", "hash", proverTask.TaskID, "proverPublicKey", proverTask.ProverPublicKey,
"taskType", message.ProofType(proverTask.TaskType).String(), "status", types.ProvingTaskVerified.String())
if err := m.updateProofStatus(ctx, proverTask, proofParameter, types.ProverProofValid, types.ProverTaskFailureTypeUndefined, proofTimeSec); err != nil {
if err := m.updateProofStatus(ctx, proverTask, proofParameter, types.ProverProofValid, types.ProverTaskFailureType(proverTask.FailureType), proofTimeSec); err != nil {
log.Error("failed to updated proof status ProvingTaskVerified", "hash", proverTask.TaskID, "proverPublicKey", proverTask.ProverPublicKey, "error", err)
return err
}
@@ -407,6 +461,9 @@ func (m *ProofReceiverLogic) updateProofStatus(ctx context.Context, proverTask *
if err != nil {
return err
}
// sync status and failture type into proverTask
proverTask.ProvingStatus = int16(status)
proverTask.FailureType = int16(failureType)
if status == types.ProverProofValid && message.ProofType(proofParameter.TaskType) == message.ProofTypeChunk {
if checkReadyErr := m.checkAreAllChunkProofsReady(ctx, proverTask.TaskID); checkReadyErr != nil {

View File

@@ -9,7 +9,7 @@ import (
)
// NewVerifier Sets up a mock verifier.
func NewVerifier(cfg *config.VerifierConfig) (*Verifier, error) {
func NewVerifier(cfg *config.VerifierConfig, _ bool) (*Verifier, error) {
return &Verifier{
cfg: cfg,
OpenVMVkMap: map[string]struct{}{"mock_vk": {}},

View File

@@ -4,6 +4,7 @@ package verifier
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -18,20 +19,36 @@ import (
"scroll-tech/coordinator/internal/config"
"scroll-tech/coordinator/internal/logic/libzkp"
"scroll-tech/coordinator/internal/utils"
)
// This struct maps to `CircuitConfig` in libzkp/src/verifier.rs
// Define a brand new struct here is to eliminate side effects in case fields
// in `*config.CircuitConfig` being changed
type rustCircuitConfig struct {
Version uint `json:"version"`
ForkName string `json:"fork_name"`
AssetsPath string `json:"assets_path"`
Features string `json:"features,omitempty"`
}
var validiumMode bool
func newRustCircuitConfig(cfg config.AssetConfig) *rustCircuitConfig {
ver := cfg.Version
if ver == 0 {
var err error
ver, err = utils.Version(cfg.ForkName, validiumMode)
if err != nil {
panic(err)
}
}
return &rustCircuitConfig{
ForkName: cfg.ForkName,
Version: uint(ver),
AssetsPath: cfg.AssetsPath,
ForkName: cfg.ForkName,
Features: cfg.Features,
}
}
@@ -59,7 +76,8 @@ type rustVkDump struct {
}
// NewVerifier Sets up a rust ffi to call verify.
func NewVerifier(cfg *config.VerifierConfig) (*Verifier, error) {
func NewVerifier(cfg *config.VerifierConfig, useValidiumMode bool) (*Verifier, error) {
validiumMode = useValidiumMode
verifierConfig := newRustVerifierConfig(cfg)
configBytes, err := json.Marshal(verifierConfig)
if err != nil {
@@ -129,6 +147,23 @@ const blocked_vks = `
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
@@ -165,17 +200,17 @@ func (v *Verifier) loadOpenVMVks(cfg config.AssetConfig) error {
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
}

View File

@@ -0,0 +1,91 @@
package utils
import (
"encoding/binary"
"fmt"
"github.com/scroll-tech/go-ethereum/common"
)
type CodecVersion uint8
const (
daBatchValidiumEncodedLength = 137
)
type DABatch interface {
SetHash(common.Hash)
}
type daBatchValidiumV1 struct {
Version CodecVersion `json:"version"`
BatchIndex uint64 `json:"batch_index"`
ParentBatchHash common.Hash `json:"parent_batch_hash"`
PostStateRoot common.Hash `json:"post_state_root"`
WithDrawRoot common.Hash `json:"withdraw_root"`
Commitment common.Hash `json:"commitment"`
}
type daBatchValidium struct {
V1 *daBatchValidiumV1 `json:"V1,omitempty"`
BatchHash common.Hash `json:"batch_hash"`
}
func (da *daBatchValidium) SetHash(h common.Hash) {
da.BatchHash = h
}
func FromVersion(v uint8) CodecVersion {
return CodecVersion(v & STFVersionMask)
}
func (c CodecVersion) DABatchForTaskFromBytes(b []byte) (DABatch, error) {
switch c {
case 1:
if v1, err := decodeDABatchV1(b); err == nil {
return &daBatchValidium{
V1: v1,
}, nil
} else {
return nil, err
}
default:
return nil, fmt.Errorf("unknown codec type %d", c)
}
}
func decodeDABatchV1(data []byte) (*daBatchValidiumV1, error) {
if len(data) != daBatchValidiumEncodedLength {
return nil, fmt.Errorf("invalid data length for DABatchV7, expected %d bytes but got %d", daBatchValidiumEncodedLength, len(data))
}
const (
versionSize = 1
indexSize = 8
hashSize = 32
)
// Offsets (same as encodeBatchHeaderValidium)
versionOffset := 0
indexOffset := versionOffset + versionSize
parentHashOffset := indexOffset + indexSize
stateRootOffset := parentHashOffset + hashSize
withdrawRootOffset := stateRootOffset + hashSize
commitmentOffset := withdrawRootOffset + hashSize
version := CodecVersion(data[versionOffset])
batchIndex := binary.BigEndian.Uint64(data[indexOffset : indexOffset+indexSize])
parentBatchHash := common.BytesToHash(data[parentHashOffset : parentHashOffset+hashSize])
postStateRoot := common.BytesToHash(data[stateRootOffset : stateRootOffset+hashSize])
withdrawRoot := common.BytesToHash(data[withdrawRootOffset : withdrawRootOffset+hashSize])
commitment := common.BytesToHash(data[commitmentOffset : commitmentOffset+hashSize])
return &daBatchValidiumV1{
Version: version,
BatchIndex: batchIndex,
ParentBatchHash: parentBatchHash,
PostStateRoot: postStateRoot,
WithDrawRoot: withdrawRoot,
Commitment: commitment,
}, nil
}

View File

@@ -0,0 +1,42 @@
package utils
import (
"errors"
"strings"
)
const (
DomainOffset = 6
STFVersionMask = (1 << DomainOffset) - 1
)
// version get the version for the chain instance
//
// TODO: This is not foolproof and does not cover all scenarios.
func Version(hardForkName string, ValidiumMode bool) (uint8, error) {
var domain, stfVersion uint8
if ValidiumMode {
domain = 1
stfVersion = 1
} else {
domain = 0
switch canonicalName := strings.ToLower(hardForkName); canonicalName {
case "euclidv1":
stfVersion = 6
case "euclidv2":
stfVersion = 7
case "feynman":
stfVersion = 8
case "galileo":
stfVersion = 9
case "galileov2":
stfVersion = 10
default:
return 0, errors.New("unknown fork name " + canonicalName)
}
}
return (domain << DomainOffset) + stfVersion, nil
}

View File

@@ -132,7 +132,7 @@ func setupCoordinator(t *testing.T, proversPerSession uint8, coordinatorURL stri
func setEnv(t *testing.T) {
var err error
version.Version = "v4.4.89"
version.Version = "v4.5.45"
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
glogger.Verbosity(log.LvlInfo)
@@ -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)

View File

@@ -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 = "main", features = ["gpu"] }
openvm-stark-sdk = { git = "ssh://git@github.com/scroll-tech/openvm-stark-gpu.git", branch = "main", features = ["gpu"] }
[patch."https://github.com/Plonky3/Plonky3.git"]
p3-air = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-field = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-commit = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-matrix = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-baby-bear = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", features = [
"nightly-features",
], tag = "v0.2.1" }
p3-koala-bear = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-util = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-challenger = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-dft = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-fri = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-goldilocks = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-keccak = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-keccak-air = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-blake3 = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-mds = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-merkle-tree = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-monty-31 = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-poseidon = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-poseidon2 = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-poseidon2-air = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-symmetric = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-uni-stark = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" }
p3-maybe-rayon = { git = "ssh://git@github.com/scroll-tech/plonky3-gpu.git", tag = "v0.2.1" } # 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", tag = "v0.2.1" }

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +0,0 @@
.PHONY: build update clean
ZKVM_COMMIT ?= freebuild
PLONKY3_GPU_VERSION=$(shell ./print_plonky3gpu_version.sh | sed -n '2p')
$(info PLONKY3_GPU_VERSION is ${PLONKY3_GPU_VERSION})
GIT_REV ?= $(shell git rev-parse --short HEAD)
GO_TAG ?= $(shell grep "var tag = " ../../common/version/version.go | cut -d "\"" -f2)
ZK_VERSION=${ZKVM_COMMIT}-${PLONKY3_GPU_VERSION}
$(info ZK_GPU_VERSION is ${ZK_VERSION})
clean:
cargo clean -Z unstable-options --release -p prover --lockfile-path ./Cargo.lock
# build gpu prover, never touch lock file
build:
GO_TAG=${GO_TAG} GIT_REV=${GIT_REV} ZK_VERSION=${ZK_VERSION} cargo build -Z unstable-options --release -p prover --lockfile-path ./Cargo.lock
# update Cargo.lock while override config has been updated
#update:
# GO_TAG=${GO_TAG} GIT_REV=${GIT_REV} ZK_VERSION=${ZK_VERSION} cargo build -Z unstable-options --release -p prover --lockfile-path ./Cargo.lock

View File

@@ -1,15 +0,0 @@
#!/bin/bash
config_file=.cargo/config.toml
plonky3_gpu_path=$(grep 'path.*plonky3-gpu' "$config_file" | cut -d'"' -f2 | head -n 1)
plonky3_gpu_path=$(dirname "$plonky3_gpu_path")
if [ -z $plonky3_gpu_path ]; then
exit 0
else
pushd $plonky3_gpu_path
commit_hash=$(git log --pretty=format:%h -n 1)
echo "${commit_hash:0:7}"
popd
fi

View File

@@ -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

View File

@@ -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")

View File

@@ -1,11 +1,11 @@
use alloy::{
providers::{Provider, ProviderBuilder, RootProvider},
providers::{Provider, ProviderBuilder},
rpc::client::ClientBuilder,
transports::layers::RetryBackoffLayer,
};
use eyre::Result;
use libzkp::tasks::ChunkInterpreter;
use sbv_primitives::types::Network;
use sbv_primitives::types::{consensus::TxL1Message, Network};
use serde::{Deserialize, Serialize};
fn default_max_retry() -> u32 {
@@ -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,80 +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 (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 {
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 {block_hash} not found"))?;
let block = provider
.get_block_by_hash(block_hash)
.full()
.await?
.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
)
})?;
let number = block.header.number;
let parent_hash = block.header.parent_hash;
if number == 0 {
eyre::bail!("no number in header or use block 0");
}
let mut witness_builder = WitnessBuilder::new()
.block(block)
.chain_id(chain_id)
.execution_witness(provider.debug_execution_witness(number.into()).await?);
let prev_state_root = match prev_witness {
Some(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
}
None => {
let parent_block = provider
.get_block_by_hash(parent_hash)
.await?
.expect("parent block should exist");
parent_block.header.state_root
}
(
chain_id,
block.header.number,
parent_block.header.state_root,
)
};
witness_builder = witness_builder.prev_state_root(prev_state_root);
Ok(witness_builder.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(
@@ -156,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
@@ -168,7 +166,41 @@ 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))
}
fn try_fetch_l1_msgs(&self, block_number: u64) -> Result<Vec<TxL1Message>> {
async fn fetch_l1_msgs(
provider: impl Provider<Network>,
block_number: u64,
) -> Result<Vec<TxL1Message>> {
let block_number_hex = format!("0x{:x}", block_number);
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum NullOrVec {
Null, // matches JSON `null`
Vec(Vec<TxL1Message>), // matches JSON array
}
Ok(
match provider
.client()
.request::<_, NullOrVec>(
"scroll_getL1MessagesInBlock",
(block_number_hex, "synced"),
)
.await?
{
NullOrVec::Null => Vec::new(),
NullOrVec::Vec(r) => r,
},
)
}
tracing::debug!("fetch L1 msgs for {block_number}");
self.handle
.block_on(fetch_l1_msgs(&self.provider, block_number))
}
}
@@ -194,10 +226,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(),
);
@@ -207,10 +239,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(),
);
@@ -223,23 +255,13 @@ mod tests {
#[test]
#[ignore = "Requires L2GETH_ENDPOINT environment variable"]
fn test_try_fetch_storage_node() {
fn test_try_fetch_l1_messages() {
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(),
);
let msgs = client.try_fetch_l1_msgs(32).expect("should success");
// 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());
println!("{}", serde_json::to_string_pretty(&msgs).unwrap());
}
}

View File

@@ -5,11 +5,12 @@ 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-types = { workspace = true, features = ["scroll"] }
scroll-zkvm-verifier.workspace = true
alloy-primitives.workspace = true #depress the effect of "native-keccak"
sbv-primitives = {workspace = true, features = ["scroll-compress-ratio", "scroll"]}
sbv-primitives = {workspace = true, features = ["scroll-compress-info", "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"] }

View File

@@ -1,28 +1,112 @@
pub mod proofs;
pub mod tasks;
pub use tasks::ProvingTaskExt;
pub mod verifier;
use verifier::HardForkName;
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, version::Version};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use std::path::Path;
use std::{collections::HashMap, path::Path, sync::OnceLock};
use tasks::chunk_interpreter::{ChunkInterpreter, TryFromWithInterpreter};
pub(crate) fn witness_use_legacy_mode(fork_name: &str) -> eyre::Result<bool> {
ADDITIONAL_FEATURES
.get()
.and_then(|features| features.get(fork_name))
.map(|cfg| cfg.legacy_witness_encoding)
.ok_or_else(|| {
eyre::eyre!(
"can not find features setting for unrecognized fork {}",
fork_name
)
})
}
#[derive(Debug, Default, Clone)]
struct FeatureOptions {
legacy_witness_encoding: bool,
for_openvm_13_prover: bool,
}
static ADDITIONAL_FEATURES: OnceLock<HashMap<HardForkName, FeatureOptions>> = OnceLock::new();
impl FeatureOptions {
pub fn new(feats: &str) -> Self {
let mut ret: Self = Default::default();
for feat_s in feats.split(':') {
match feat_s.trim().to_lowercase().as_str() {
"legacy_witness" => {
tracing::info!("set witness encoding for legacy mode");
ret.legacy_witness_encoding = true;
}
"openvm_13" => {
tracing::info!("set prover should use openvm 13");
ret.for_openvm_13_prover = true;
}
s => tracing::warn!("unrecognized dynamic feature: {s}"),
}
}
ret
}
}
/// Turn the coordinator's chunk task into a json string for formal chunk proving
/// task (with full witnesses)
pub fn checkout_chunk_task(
task_json: &str,
decryption_key: Option<&[u8]>,
interpreter: impl ChunkInterpreter,
) -> eyre::Result<String> {
let chunk_task = serde_json::from_str::<tasks::ChunkTask>(task_json)?;
let ret = serde_json::to_string(&tasks::ChunkProvingTask::try_from_with_interpret(
chunk_task,
interpreter,
)?)?;
Ok(ret)
Ok(serde_json::to_string(
&tasks::ChunkProvingTask::try_from_with_interpret(chunk_task, decryption_key, interpreter)?,
)?)
}
/// Convert the universal task json into compatible form for old prover
pub fn univ_task_compatibility_fix(task_json: &str) -> eyre::Result<String> {
use scroll_zkvm_types::proof::VmInternalStarkProof;
let task: tasks::ProvingTask = serde_json::from_str(task_json)?;
let aggregated_proofs: Vec<VmInternalStarkProof> = task
.aggregated_proofs
.into_iter()
.map(|proof| VmInternalStarkProof {
proofs: proof.proofs,
public_values: proof.public_values,
})
.collect();
#[derive(Serialize)]
struct CompatibleProvingTask {
/// seralized witness which should be written into stdin first
pub serialized_witness: Vec<Vec<u8>>,
/// aggregated proof carried by babybear fields, should be written into stdin
/// followed `serialized_witness`
pub aggregated_proofs: Vec<VmInternalStarkProof>,
/// Fork name specify
pub fork_name: String,
/// The vk of app which is expcted to prove this task
pub vk: Vec<u8>,
/// An identifier assigned by coordinator, it should be kept identify for the
/// same task (for example, using chunk, batch and bundle hashes)
pub identifier: String,
}
let compatible_u_task = CompatibleProvingTask {
serialized_witness: task.serialized_witness,
aggregated_proofs,
fork_name: task.fork_name,
vk: task.vk,
identifier: task.identifier,
};
Ok(serde_json::to_string(&compatible_u_task)?)
}
/// Generate required staff for proving tasks
@@ -32,7 +116,6 @@ pub fn gen_universal_task(
task_json: &str,
fork_name_str: &str,
expected_vk: &[u8],
interpreter: Option<impl ChunkInterpreter>,
) -> eyre::Result<(B256, String, String)> {
use proofs::*;
use tasks::*;
@@ -51,36 +134,56 @@ pub fn gen_universal_task(
let mut task = serde_json::from_str::<ChunkProvingTask>(task_json)?;
// normailze fork name field in task
task.fork_name = task.fork_name.to_lowercase();
let version = Version::from(task.version);
// 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) = utils::panic_catch(move || {
gen_universal_chunk_task(task, fork_name_str.into(), interpreter)
})
.map_err(|e| eyre::eyre!("caught panic in chunk task{e}"))??;
if fork_name_str != version.fork.as_str() {
eyre::bail!(
"given task version, expected fork={fork_name_str}, got={version_fork}",
version_fork = version.fork.as_str()
);
}
let (pi_hash, metadata, u_task) =
utils::panic_catch(move || gen_universal_chunk_task(task))
.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 mut task = serde_json::from_str::<BatchProvingTask>(task_json)?;
task.fork_name = task.fork_name.to_lowercase();
let version = Version::from(task.version);
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);
}
if fork_name_str != version.fork.as_str() {
eyre::bail!(
"given task version, expected fork={fork_name_str}, got={version_fork}",
version_fork = version.fork.as_str()
);
}
let (pi_hash, metadata, u_task) =
utils::panic_catch(move || gen_universal_batch_task(task, fork_name_str.into()))
utils::panic_catch(move || gen_universal_batch_task(task))
.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 mut task = serde_json::from_str::<BundleProvingTask>(task_json)?;
task.fork_name = task.fork_name.to_lowercase();
let version = Version::from(task.version);
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);
}
if fork_name_str != version.fork.as_str() {
eyre::bail!(
"given task version, expected fork={fork_name_str}, got={version_fork}",
version_fork = version.fork.as_str()
);
}
let (pi_hash, metadata, u_task) =
utils::panic_catch(move || gen_universal_bundle_task(task, fork_name_str.into()))
utils::panic_catch(move || gen_universal_bundle_task(task))
.map_err(|e| eyre::eyre!("caught panic in chunk task{e}"))??;
(pi_hash, AnyMetaData::Bundle(metadata), u_task)
}
@@ -88,11 +191,26 @@ pub fn gen_universal_task(
};
u_task.vk = Vec::from(expected_vk);
let fork_name = u_task.fork_name.clone();
let mut u_task_ext = ProvingTaskExt::new(u_task);
// set additional settings from global features
if let Some(cfg) = ADDITIONAL_FEATURES
.get()
.and_then(|features| features.get(&fork_name))
{
u_task_ext.use_openvm_13 = cfg.for_openvm_13_prover;
} else {
tracing::warn!(
"can not found features setting for unrecognized fork {}",
fork_name
);
}
Ok((
pi_hash,
serde_json::to_string(&metadata)?,
serde_json::to_string(&u_task)?,
serde_json::to_string(&u_task_ext)?,
))
}
@@ -123,7 +241,26 @@ pub fn gen_wrapped_proof(proof_json: &str, metadata: &str, vk: &[u8]) -> eyre::R
/// init verifier
pub fn verifier_init(config: &str) -> eyre::Result<()> {
let cfg: VerifierConfig = serde_json::from_str(config)?;
ADDITIONAL_FEATURES
.set(HashMap::from_iter(cfg.circuits.iter().map(|config| {
tracing::info!(
"start setting features [{:?}] for fork {}",
config.features,
config.fork_name
);
(
config.fork_name.to_lowercase(),
config
.features
.as_ref()
.map(|features| FeatureOptions::new(features.as_str()))
.unwrap_or_default(),
)
})))
.map_err(|c| eyre::eyre!("Fail to init additional features: {c:?}"))?;
verifier::init(cfg);
Ok(())
}

View File

@@ -7,10 +7,11 @@ use scroll_zkvm_types::{
batch::BatchInfo,
bundle::BundleInfo,
chunk::ChunkInfo,
proof::{EvmProof, OpenVmEvmProof, ProofEnum, RootProof},
public_inputs::{ForkName, MultiVersionPublicInputs},
types_agg::{AggregationInput, ProgramCommitment},
util::vec_as_base64,
proof::{EvmProof, OpenVmEvmProof, ProofEnum, StarkProof},
public_inputs::MultiVersionPublicInputs,
types_agg::AggregationInput,
utils::{serialize_vk, vec_as_base64},
version,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@@ -40,7 +41,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 +62,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 +123,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 {
@@ -137,8 +140,6 @@ impl ProofMetadata for ChunkProofMetadata {
pub struct BatchProofMetadata {
/// The batch information describing the list of chunks.
pub batch_info: BatchInfo,
/// The [`scroll_zkvm_types::batch::BatchHeader`]'s digest.
pub batch_hash: B256,
}
impl ProofMetadata for BatchProofMetadata {
@@ -170,7 +171,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),
}
}
}
@@ -179,13 +180,13 @@ impl<Metadata: ProofMetadata> WrappedProof<Metadata> {
/// Sanity checks on the wrapped proof:
///
/// - pi_hash computed in host does in fact match pi_hash computed in guest
pub fn pi_hash_check(&self, fork_name: ForkName) -> bool {
pub fn pi_hash_check(&self, ver: version::Version) -> bool {
let proof_pi = self.proof.public_values();
let expected_pi = self
.metadata
.pi_hash_info()
.pi_hash_by_fork(fork_name)
.pi_hash_by_version(ver)
.0
.as_ref()
.iter()
@@ -214,7 +215,7 @@ impl<Metadata: ProofMetadata> PersistableProof for WrappedProof<Metadata> {
mod tests {
use base64::{prelude::BASE64_STANDARD, Engine};
use sbv_primitives::B256;
use scroll_zkvm_types::{bundle::BundleInfo, proof::EvmProof, public_inputs::ForkName};
use scroll_zkvm_types::{bundle::BundleInfo, proof::EvmProof};
use super::*;
@@ -250,8 +251,9 @@ mod tests {
batch_hash: B256::repeat_byte(4),
withdraw_root: B256::repeat_byte(5),
msg_queue_hash: B256::repeat_byte(6),
encryption_key: None,
};
let bundle_pi_hash = bundle_info.pi_hash(ForkName::EuclidV1);
let bundle_pi_hash = bundle_info.pi_hash_euclidv1();
BundleProofMetadata {
bundle_info,
bundle_pi_hash,

View File

@@ -10,46 +10,66 @@ pub use chunk_interpreter::ChunkInterpreter;
pub use scroll_zkvm_types::task::ProvingTask;
use crate::{
proofs::{self, BatchProofMetadata, BundleProofMetadata, ChunkProofMetadata},
proofs::{BatchProofMetadata, BundleProofMetadata, ChunkProofMetadata},
utils::panic_catch,
};
use sbv_primitives::B256;
use scroll_zkvm_types::public_inputs::{ForkName, MultiVersionPublicInputs};
use scroll_zkvm_types::public_inputs::{MultiVersionPublicInputs, Version};
fn check_aggregation_proofs<Metadata>(
proofs: &[proofs::WrappedProof<Metadata>],
fork_name: ForkName,
) -> eyre::Result<()>
where
Metadata: proofs::ProofMetadata,
{
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: MultiVersionPublicInputs>(
metadata: &[Metadata],
version: Version,
) -> eyre::Result<()> {
panic_catch(|| {
for w in proofs.windows(2) {
w[1].metadata
.pi_hash_info()
.validate(w[0].metadata.pi_hash_info(), fork_name);
for w in metadata.windows(2) {
w[1].validate(&w[0], version);
}
})
.map_err(|e| eyre::eyre!("Chunk data validation failed: {}", e))?;
.map_err(|e| eyre::eyre!("Metadata validation failed: {}", e))?;
Ok(())
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ProvingTaskExt {
#[serde(flatten)]
task: ProvingTask,
#[serde(default)]
pub use_openvm_13: bool,
}
impl From<ProvingTaskExt> for ProvingTask {
fn from(wrap_t: ProvingTaskExt) -> Self {
wrap_t.task
}
}
impl ProvingTaskExt {
pub fn new(task: ProvingTask) -> Self {
Self {
task,
use_openvm_13: false,
}
}
}
/// Generate required staff for chunk proving
pub fn gen_universal_chunk_task(
mut task: ChunkProvingTask,
fork_name: ForkName,
interpreter: Option<impl ChunkInterpreter>,
task: ChunkProvingTask,
) -> eyre::Result<(B256, ChunkProofMetadata, ProvingTask)> {
if let Some(interpreter) = interpreter {
task.prepare_task_via_interpret(interpreter)?;
}
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);
let chunk_total_gas = task.stats().total_gas_used;
let (proving_task, chunk_info, chunk_pi_hash) = task.into_proving_task_with_precheck()?;
Ok((
expected_pi_hash,
ChunkProofMetadata { chunk_info },
chunk_pi_hash,
ChunkProofMetadata {
chunk_info,
chunk_total_gas,
},
proving_task,
))
}
@@ -57,18 +77,11 @@ pub fn gen_universal_chunk_task(
/// Generate required staff for batch proving
pub fn gen_universal_batch_task(
task: BatchProvingTask,
fork_name: ForkName,
) -> eyre::Result<(B256, BatchProofMetadata, ProvingTask)> {
let batch_info = task.precheck_and_build_metadata()?;
let proving_task = task.try_into()?;
let expected_pi_hash = batch_info.pi_hash_by_fork(fork_name);
let (proving_task, batch_info, batch_pi_hash) = task.into_proving_task_with_precheck()?;
Ok((
expected_pi_hash,
BatchProofMetadata {
batch_info,
batch_hash: expected_pi_hash,
},
batch_pi_hash,
BatchProofMetadata { batch_info },
proving_task,
))
}
@@ -76,17 +89,13 @@ pub fn gen_universal_batch_task(
/// Generate required staff for bundle proving
pub fn gen_universal_bundle_task(
task: BundleProvingTask,
fork_name: ForkName,
) -> eyre::Result<(B256, BundleProofMetadata, ProvingTask)> {
let bundle_info = task.precheck_and_build_metadata()?;
let proving_task = task.try_into()?;
let expected_pi_hash = bundle_info.pi_hash_by_fork(fork_name);
let (proving_task, bundle_info, bundle_pi_hash) = task.into_proving_task_with_precheck()?;
Ok((
expected_pi_hash,
bundle_pi_hash,
BundleProofMetadata {
bundle_info,
bundle_pi_hash: expected_pi_hash,
bundle_pi_hash,
},
proving_task,
))

View File

@@ -1,58 +1,92 @@
use crate::proofs::ChunkProof;
use c_kzg::Bytes48;
use eyre::Result;
use sbv_primitives::{B256, U256};
use scroll_zkvm_types::{
batch::{
BatchHeader, BatchHeaderV6, BatchHeaderV7, BatchHeaderV8, BatchInfo, BatchWitness,
Envelope, EnvelopeV6, EnvelopeV7, EnvelopeV8, PointEvalWitness, ReferenceHeader,
ToArchievedWitness, N_BLOB_BYTES,
build_point_eval_witness, BatchHeader, BatchHeaderV6, BatchHeaderV7, BatchHeaderValidium,
BatchInfo, BatchWitness, Envelope, EnvelopeV6, EnvelopeV7, LegacyBatchWitness,
ReferenceHeader, N_BLOB_BYTES,
},
public_inputs::ForkName,
chunk::ChunkInfo,
public_inputs::{ForkName, MultiVersionPublicInputs, Version},
task::ProvingTask,
utils::{to_rkyv_bytes, RancorError},
version::{Codec, Domain, STFVersion},
};
use crate::proofs::ChunkProof;
mod utils;
use utils::{base64, point_eval};
/// Define variable batch header type, since BatchHeaderV6 can not
/// be decoded as V7 we can always has correct deserialization
/// Notice: V6 header MUST be put above V7 since untagged enum
/// try to decode each defination in order
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct BatchHeaderValidiumWithHash {
#[serde(flatten)]
header: BatchHeaderValidium,
batch_hash: B256,
}
/// Parse header types passed from golang side and adapt to the
/// definition in zkvm-prover's types
/// We distinguish the header type in golang side according to the STF
/// version, i.e. v6, v7-v10 (current), and validium
/// And adapt it to the corresponding batch header type used in zkvm-prover's witness
/// definition, i.e. v6, v7 (current), and validium
#[derive(Clone, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
#[allow(non_camel_case_types)]
pub enum BatchHeaderV {
/// Header for validium mode.
Validium(BatchHeaderValidiumWithHash),
/// Header for scroll's STF version v6.
V6(BatchHeaderV6),
V7_8(BatchHeaderV7),
/// Header for scroll's STF versions v7 - v10.
///
/// Since the codec essentially is unchanged for the above STF versions, we do not define new
/// variants, instead re-using the [`BatchHeaderV7`] variant.
V7_to_V10(BatchHeaderV7),
}
impl core::fmt::Display for BatchHeaderV {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BatchHeaderV::V6(_) => write!(f, "V6"),
BatchHeaderV::V7_to_V10(_) => write!(f, "V7 - V10"),
BatchHeaderV::Validium(_) => write!(f, "Validium"),
}
}
}
impl BatchHeaderV {
pub fn batch_hash(&self) -> B256 {
match self {
BatchHeaderV::V6(h) => h.batch_hash(),
BatchHeaderV::V7_8(h) => h.batch_hash(),
BatchHeaderV::V7_to_V10(h) => h.batch_hash(),
BatchHeaderV::Validium(h) => h.header.batch_hash(),
}
}
pub fn must_v6_header(&self) -> &BatchHeaderV6 {
pub fn to_zkvm_batch_header_v6(&self) -> &BatchHeaderV6 {
match self {
BatchHeaderV::V6(h) => h,
_ => panic!("try to pick other header type"),
_ => unreachable!("A header of {} is considered to be v6", self),
}
}
pub fn must_v7_header(&self) -> &BatchHeaderV7 {
pub fn to_zkvm_batch_header_v7_to_v10(&self) -> &BatchHeaderV7 {
match self {
BatchHeaderV::V7_8(h) => h,
_ => panic!("try to pick other header type"),
BatchHeaderV::V7_to_V10(h) => h,
_ => unreachable!(
"A header of {} is considered to be in [v7, v8, v9, v10]",
self
),
}
}
pub fn must_v8_header(&self) -> &BatchHeaderV8 {
pub fn to_zkvm_batch_header_validium(&self) -> &BatchHeaderValidium {
match self {
BatchHeaderV::V7_8(h) => h,
_ => panic!("try to pick other header type"),
BatchHeaderV::Validium(h) => &h.header,
_ => unreachable!("A header of {} is considered to be validium", self),
}
}
}
@@ -61,6 +95,8 @@ impl BatchHeaderV {
/// is compatible with both pre-euclidv2 and euclidv2
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct BatchProvingTask {
/// The version of the chunks in the batch, as per [`Version`].
pub version: u8,
/// Chunk proofs for the contiguous list of chunks within the batch.
pub chunk_proofs: Vec<ChunkProof>,
/// The [`BatchHeaderV6/V7`], as computed on-chain for this batch.
@@ -79,128 +115,253 @@ pub struct BatchProvingTask {
pub fork_name: String,
}
impl TryFrom<BatchProvingTask> for ProvingTask {
type Error = eyre::Error;
impl BatchProvingTask {
pub fn into_proving_task_with_precheck(self) -> Result<(ProvingTask, BatchInfo, B256)> {
let (witness, metadata, batch_pi_hash) = self.precheck()?;
let serialized_witness = if crate::witness_use_legacy_mode(&self.fork_name)? {
let legacy_witness = LegacyBatchWitness::from(witness);
to_rkyv_bytes::<RancorError>(&legacy_witness)?.into_vec()
} else {
super::encode_task_to_witness(&witness)?
};
fn try_from(value: BatchProvingTask) -> Result<Self> {
let witness = value.build_guest_input();
Ok(ProvingTask {
identifier: value.batch_header.batch_hash().to_string(),
fork_name: value.fork_name,
aggregated_proofs: value
let proving_task = ProvingTask {
identifier: self.batch_header.batch_hash().to_string(),
fork_name: self.fork_name,
aggregated_proofs: self
.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(),
})
};
Ok((proving_task, metadata, batch_pi_hash))
}
}
impl BatchProvingTask {
fn build_guest_input(&self) -> BatchWitness {
let fork_name = self.fork_name.to_lowercase().as_str().into();
fn build_guest_input(&self, version: Version) -> BatchWitness {
tracing::info!(
"Handling batch task for input, version byte {}, Version data: {:?}",
self.version,
version
);
// sanity check for if result of header type parsing match to version
match &self.batch_header {
BatchHeaderV::Validium(_) => assert!(
version.is_validium(),
"version {:?} is not match with parsed header, get validium header but version is not validium", version,
),
BatchHeaderV::V6(_) => assert_eq!(version.fork, ForkName::EuclidV1,
"hardfork mismatch for da-codec@v6 header: found={:?}, expected={:?}",
version.fork,
ForkName::EuclidV1,
),
BatchHeaderV::V7_to_V10(_) => assert!(
matches!(version.fork, ForkName::EuclidV2 | ForkName::Feynman | ForkName::Galileo | ForkName::GalileoV2),
"hardfork mismatch for da-codec@v7/8/9/10 header: found={}, expected={:?}",
version.fork,
[ForkName::EuclidV2, ForkName::Feynman, ForkName::Galileo, ForkName::GalileoV2],
),
}
// sanity check: calculate point eval needed and compare with task input
let (kzg_commitment, kzg_proof, challenge_digest) = {
let blob = point_eval::to_blob(&self.blob_bytes);
let commitment = point_eval::blob_to_kzg_commitment(&blob);
let versioned_hash = point_eval::get_versioned_hash(&commitment);
let challenge_digest = match &self.batch_header {
BatchHeaderV::V6(_) => {
assert_eq!(
fork_name,
ForkName::EuclidV1,
"hardfork mismatch for da-codec@v6 header: found={fork_name:?}, expected={:?}",
ForkName::EuclidV1,
);
EnvelopeV6::from_slice(self.blob_bytes.as_slice())
.challenge_digest(versioned_hash)
}
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
};
let point_eval_witness = if !version.is_validium() {
// sanity check: calculate point eval needed and compare with task input
let (kzg_commitment, kzg_proof, challenge_digest) = {
let blob = point_eval::to_blob(&self.blob_bytes);
let commitment = point_eval::blob_to_kzg_commitment(&blob);
let versioned_hash = point_eval::get_versioned_hash(&commitment);
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],
),
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
};
let challenge_digest = match version.codec {
Codec::V6 => {
// notice v6 do not use padded blob bytes
<EnvelopeV6 as Envelope>::from_slice(self.blob_bytes.as_slice())
.challenge_digest(versioned_hash)
}
}
Codec::V7 => <EnvelopeV7 as Envelope>::from_slice(padded_blob_bytes.as_slice())
.challenge_digest(versioned_hash),
};
let (proof, _) = point_eval::get_kzg_proof(&blob, challenge_digest);
(commitment.to_bytes(), proof.to_bytes(), challenge_digest)
};
let (proof, _) = point_eval::get_kzg_proof(&blob, challenge_digest);
if let Some(k) = self.kzg_commitment {
assert_eq!(k, kzg_commitment);
}
(commitment.to_bytes(), proof.to_bytes(), challenge_digest)
if let Some(c) = self.challenge_digest {
assert_eq!(c, U256::from_be_bytes(challenge_digest.0));
}
if let Some(p) = self.kzg_proof {
assert_eq!(p, kzg_proof);
}
Some(build_point_eval_witness(
kzg_commitment.into_inner(),
kzg_proof.into_inner(),
))
} else {
assert!(self.kzg_proof.is_none(), "domain=validium has no blob-da");
assert!(
self.kzg_commitment.is_none(),
"domain=validium has no blob-da"
);
assert!(
self.challenge_digest.is_none(),
"domain=validium has no blob-da"
);
match &self.batch_header {
BatchHeaderV::Validium(h) => assert_eq!(
h.header.batch_hash(),
h.batch_hash,
"calculated batch hash match which from coordinator"
),
_ => panic!("unexpected header type"),
}
None
};
if let Some(k) = self.kzg_commitment {
assert_eq!(k, kzg_commitment);
}
if let Some(c) = self.challenge_digest {
assert_eq!(c, U256::from_be_bytes(challenge_digest.0));
}
if let Some(p) = self.kzg_proof {
assert_eq!(p, kzg_proof);
}
let point_eval_witness = PointEvalWitness {
kzg_commitment: kzg_commitment.into_inner(),
kzg_proof: kzg_proof.into_inner(),
let reference_header = match (version.domain, version.stf_version) {
(Domain::Scroll, STFVersion::V6) => {
ReferenceHeader::V6(*self.batch_header.to_zkvm_batch_header_v6())
}
// The da-codec for STF versions v7, v8, v9, v10 is identical. In zkvm-prover we do not
// create additional variants to indicate the identical behaviour of codec. Instead we
// add a separate variant for the STF version.
//
// We handle the different STF versions here however build the same batch header since
// that type does not change. The batch header's version byte constructed in the
// coordinator actually defines the STF version (v7, v8 or v9, v10) and we can derive
// the hard-fork (e.g. feynman or galileo) and the codec from the version
// byte.
//
// Refer [`scroll_zkvm_types::public_inputs::Version`].
(
Domain::Scroll,
STFVersion::V7 | STFVersion::V8 | STFVersion::V9 | STFVersion::V10,
) => ReferenceHeader::V7_V8_V9(*self.batch_header.to_zkvm_batch_header_v7_to_v10()),
(Domain::Validium, STFVersion::V1) => {
ReferenceHeader::Validium(*self.batch_header.to_zkvm_batch_header_validium())
}
(domain, stf_version) => {
unreachable!("unsupported domain={domain:?},stf-version={stf_version:?}")
}
};
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()),
};
// patch: ensure block_hash field is ZERO for scroll domain
let chunk_infos = self
.chunk_proofs
.iter()
.map(|p| {
if version.domain == Domain::Scroll {
ChunkInfo {
prev_blockhash: B256::ZERO,
post_blockhash: B256::ZERO,
..p.metadata.chunk_info.clone()
}
} else {
p.metadata.chunk_info.clone()
}
})
.collect();
BatchWitness {
fork_name,
version: version.as_version_byte(),
fork_name: version.fork,
chunk_proofs: self.chunk_proofs.iter().map(|proof| proof.into()).collect(),
chunk_infos: self
.chunk_proofs
.iter()
.map(|p| p.metadata.chunk_info.clone())
.collect(),
chunk_infos,
blob_bytes: self.blob_bytes.clone(),
reference_header,
point_eval_witness,
}
}
pub fn precheck_and_build_metadata(&self) -> Result<BatchInfo> {
let fork_name = ForkName::from(self.fork_name.as_str());
pub fn precheck(&self) -> Result<(BatchWitness, BatchInfo, B256)> {
// for every aggregation task, there are two steps needed to build the metadata:
// 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 version = Version::from(self.version);
let witness = self.build_guest_input(version);
let metadata = BatchInfo::from(&witness);
super::check_aggregation_proofs(
witness.chunk_infos.as_slice(),
Version::from(self.version),
)?;
let pi_hash = metadata.pi_hash_by_version(version);
super::check_aggregation_proofs(self.chunk_proofs.as_slice(), fork_name)?;
Ok(metadata)
Ok((witness, metadata, pi_hash))
}
}
#[test]
fn test_deserde_batch_header_v_validium() {
use std::str::FromStr;
// Top-level JSON: flattened enum tag "V1" + batch_hash
let json = r#"{
"V1": {
"version": 1,
"batch_index": 42,
"parent_batch_hash": "0x1111111111111111111111111111111111111111111111111111111111111111",
"post_state_root": "0x2222222222222222222222222222222222222222222222222222222222222222",
"withdraw_root": "0x3333333333333333333333333333333333333333333333333333333333333333",
"commitment": "0x4444444444444444444444444444444444444444444444444444444444444444"
},
"batch_hash": "0x5555555555555555555555555555555555555555555555555555555555555555"
}"#;
let parsed: BatchHeaderV = serde_json::from_str(json).expect("deserialize BatchHeaderV");
match parsed {
BatchHeaderV::Validium(v) => {
// Check the batch_hash field
let expected_batch_hash = B256::from_str(
"0x5555555555555555555555555555555555555555555555555555555555555555",
)
.unwrap();
assert_eq!(v.batch_hash, expected_batch_hash);
// Check the inner header variant and fields
match v.header {
BatchHeaderValidium::V1(h) => {
assert_eq!(h.version, 1);
assert_eq!(h.batch_index, 42);
let p = B256::from_str(
"0x1111111111111111111111111111111111111111111111111111111111111111",
)
.unwrap();
let s = B256::from_str(
"0x2222222222222222222222222222222222222222222222222222222222222222",
)
.unwrap();
let w = B256::from_str(
"0x3333333333333333333333333333333333333333333333333333333333333333",
)
.unwrap();
let c = B256::from_str(
"0x4444444444444444444444444444444444444444444444444444444444444444",
)
.unwrap();
assert_eq!(h.parent_batch_hash, p);
assert_eq!(h.post_state_root, s);
assert_eq!(h.withdraw_root, w);
assert_eq!(h.commitment, c);
// Sanity: computed batch hash equals the provided one (if method available)
// assert_eq!(v.header.batch_hash(), expected_batch_hash);
}
}
}
_ => panic!("expected validium header variant"),
}
}

View File

@@ -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.
///

View File

@@ -1,16 +1,22 @@
use crate::proofs::BatchProof;
use eyre::Result;
use sbv_primitives::B256;
use scroll_zkvm_types::{
bundle::{BundleInfo, BundleWitness, ToArchievedWitness},
public_inputs::ForkName,
bundle::{BundleInfo, BundleWitness, LegacyBundleWitness},
public_inputs::{MultiVersionPublicInputs, Version},
task::ProvingTask,
utils::{to_rkyv_bytes, RancorError},
};
use crate::proofs::BatchProof;
/// Message indicating a sanity check failure.
const BUNDLE_SANITY_MSG: &str = "bundle must have at least one batch";
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct BundleProvingTask {
/// The version of batches in the bundle.
pub version: u8,
/// The STARK proofs of each batch in the bundle.
pub batch_proofs: Vec<BatchProof>,
/// for sanity check
pub bundle_info: Option<BundleInfo>,
@@ -19,6 +25,30 @@ pub struct BundleProvingTask {
}
impl BundleProvingTask {
pub fn into_proving_task_with_precheck(self) -> Result<(ProvingTask, BundleInfo, B256)> {
let (witness, bundle_info, bundle_pi_hash) = self.precheck()?;
let serialized_witness = if crate::witness_use_legacy_mode(&self.fork_name)? {
let legacy = LegacyBundleWitness::from(witness);
to_rkyv_bytes::<RancorError>(&legacy)?.into_vec()
} else {
super::encode_task_to_witness(&witness)?
};
let proving_task = ProvingTask {
identifier: self.identifier(),
fork_name: self.fork_name,
aggregated_proofs: self
.batch_proofs
.into_iter()
.map(|w_proof| w_proof.proof.into_stark_proof().expect("expect root proof"))
.collect(),
serialized_witness: vec![serialized_witness],
vk: Vec::new(),
};
Ok((proving_task, bundle_info, bundle_pi_hash))
}
fn identifier(&self) -> String {
assert!(!self.batch_proofs.is_empty(), "{BUNDLE_SANITY_MSG}",);
@@ -27,64 +57,45 @@ impl BundleProvingTask {
.first()
.expect(BUNDLE_SANITY_MSG)
.metadata
.batch_info
.batch_hash,
self.batch_proofs
.last()
.expect(BUNDLE_SANITY_MSG)
.metadata
.batch_info
.batch_hash,
);
format!("{first}-{last}")
}
fn build_guest_input(&self) -> BundleWitness {
fn build_guest_input(&self, version: Version) -> BundleWitness {
BundleWitness {
version: version.as_version_byte(),
batch_proofs: self.batch_proofs.iter().map(|proof| proof.into()).collect(),
batch_infos: self
.batch_proofs
.iter()
.map(|wrapped_proof| wrapped_proof.metadata.batch_info.clone())
.collect(),
fork_name: self.fork_name.to_lowercase().as_str().into(),
fork_name: version.fork,
}
}
pub fn precheck_and_build_metadata(&self) -> Result<BundleInfo> {
let fork_name = ForkName::from(self.fork_name.as_str());
fn precheck(&self) -> Result<(BundleWitness, BundleInfo, B256)> {
// for every aggregation task, there are two steps needed to build the metadata:
// 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 version = Version::from(self.version);
let witness = self.build_guest_input(version);
let metadata = BundleInfo::from(&witness);
super::check_aggregation_proofs(
witness.batch_infos.as_slice(),
Version::from(self.version),
)?;
let pi_hash = metadata.pi_hash_by_version(version);
super::check_aggregation_proofs(self.batch_proofs.as_slice(), fork_name)?;
Ok(metadata)
}
}
impl TryFrom<BundleProvingTask> for ProvingTask {
type Error = eyre::Error;
fn try_from(value: BundleProvingTask) -> Result<Self> {
let witness = value.build_guest_input();
Ok(ProvingTask {
identifier: value.identifier(),
fork_name: value.fork_name,
aggregated_proofs: value
.batch_proofs
.into_iter()
.map(|w_proof| w_proof.proof.into_root_proof().expect("expect root proof"))
.collect(),
serialized_witness: vec![witness.rkyv_serialize(None)?.to_vec()],
vk: Vec::new(),
})
Ok((witness, metadata, pi_hash))
}
}

View File

@@ -1,18 +1,26 @@
use super::chunk_interpreter::*;
use eyre::Result;
use sbv_primitives::{types::BlockWitness, B256};
use sbv_core::BlockWitness;
use sbv_primitives::{types::consensus::BlockHeader, B256};
use scroll_zkvm_types::{
chunk::{execute, ChunkInfo, ChunkWitness, ToArchievedWitness},
chunk::{execute, ChunkInfo, ChunkWitness, LegacyChunkWitness, ValidiumInputs},
public_inputs::{MultiVersionPublicInputs, Version},
task::ProvingTask,
utils::{to_rkyv_bytes, RancorError},
};
use super::chunk_interpreter::*;
/// The type aligned with coordinator's defination
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChunkTask {
/// The version for the chunk, as per [`Version`].
pub version: u8,
/// block hashes for a series of block
pub block_hashes: Vec<B256>,
/// The on-chain L1 msg queue hash before applying L1 msg txs from the chunk.
pub prev_msg_queue_hash: B256,
/// The on-chain L1 msg queue hash after applying L1 msg txs from the chunk (for validate)
pub post_msg_queue_hash: B256,
/// Fork name specify
pub fork_name: String,
}
@@ -20,6 +28,7 @@ pub struct ChunkTask {
impl TryFromWithInterpreter<ChunkTask> for ChunkProvingTask {
fn try_from_with_interpret(
value: ChunkTask,
decryption_key: Option<&[u8]>,
interpreter: impl ChunkInterpreter,
) -> Result<Self> {
let mut block_witnesses = Vec::new();
@@ -29,10 +38,28 @@ impl TryFromWithInterpreter<ChunkTask> for ChunkProvingTask {
block_witnesses.push(witness);
}
let validium_txs = if Version::from(value.version).is_validium() {
let mut validium_txs = Vec::new();
for block_number in block_witnesses.iter().map(|w| w.header.number()) {
validium_txs.push(interpreter.try_fetch_l1_msgs(block_number)?);
}
validium_txs
} else {
vec![]
};
let validium_inputs = decryption_key.map(|secret_key| ValidiumInputs {
validium_txs,
secret_key: secret_key.into(),
});
Ok(Self {
version: value.version,
block_witnesses,
prev_msg_queue_hash: value.prev_msg_queue_hash,
post_msg_queue_hash: value.post_msg_queue_hash,
fork_name: value.fork_name,
validium_inputs,
})
}
}
@@ -46,12 +73,18 @@ const CHUNK_SANITY_MSG: &str = "chunk must have at least one block";
/// - {first_block_number}-{last_block_number}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct ChunkProvingTask {
/// The version for the chunk, as per [Version][scroll_zkvm_types::version::Version].
pub version: u8,
/// Witnesses for every block in the chunk.
pub block_witnesses: Vec<BlockWitness>,
/// The on-chain L1 msg queue hash before applying L1 msg txs from the chunk.
pub prev_msg_queue_hash: B256,
/// The on-chain L1 msg queue hash after applying L1 msg txs from the chunk (for validate)
pub post_msg_queue_hash: B256,
/// Fork name specify
pub fork_name: String,
/// Optional inputs in case of domain=validium.
pub validium_inputs: Option<ValidiumInputs>,
}
#[derive(Clone, Debug)]
@@ -61,29 +94,13 @@ pub struct ChunkDetails {
pub total_gas_used: u64,
}
impl TryFrom<ChunkProvingTask> for ProvingTask {
type Error = eyre::Error;
fn try_from(value: ChunkProvingTask) -> Result<Self> {
let witness = value.build_guest_input();
Ok(ProvingTask {
identifier: value.identifier(),
fork_name: value.fork_name,
aggregated_proofs: Vec::new(),
serialized_witness: vec![witness.rkyv_serialize(None)?.to_vec()],
vk: Vec::new(),
})
}
}
impl ChunkProvingTask {
pub fn stats(&self) -> ChunkDetails {
let num_blocks = self.block_witnesses.len();
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
@@ -98,6 +115,26 @@ impl ChunkProvingTask {
}
}
pub fn into_proving_task_with_precheck(self) -> Result<(ProvingTask, ChunkInfo, B256)> {
let (witness, chunk_info, chunk_pi_hash) = self.precheck()?;
let serialized_witness = if crate::witness_use_legacy_mode(&self.fork_name)? {
let legacy_witness = LegacyChunkWitness::from(witness);
to_rkyv_bytes::<RancorError>(&legacy_witness)?.into_vec()
} else {
super::encode_task_to_witness(&witness)?
};
let proving_task = ProvingTask {
identifier: self.identifier(),
fork_name: self.fork_name,
aggregated_proofs: Vec::new(),
serialized_witness: vec![serialized_witness],
vk: Vec::new(),
};
Ok((proving_task, chunk_info, chunk_pi_hash))
}
fn identifier(&self) -> String {
assert!(!self.block_witnesses.is_empty(), "{CHUNK_SANITY_MSG}",);
@@ -117,32 +154,42 @@ impl ChunkProvingTask {
format!("{first}-{last}")
}
fn build_guest_input(&self) -> ChunkWitness {
ChunkWitness::new(
&self.block_witnesses,
self.prev_msg_queue_hash,
self.fork_name.to_lowercase().as_str().into(),
)
fn build_guest_input(&self, version: Version) -> ChunkWitness {
if version.is_validium() {
assert!(self.validium_inputs.is_some());
ChunkWitness::new(
version.as_version_byte(),
&self.block_witnesses,
self.prev_msg_queue_hash,
version.fork,
self.validium_inputs.clone(),
)
} else {
ChunkWitness::new_scroll(
version.as_version_byte(),
&self.block_witnesses,
self.prev_msg_queue_hash,
version.fork,
)
}
}
fn insert_state(&mut self, node: sbv_primitives::Bytes) {
self.block_witnesses[0].states.push(node);
}
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}"))?;
Ok(ret)
fn precheck(&self) -> Result<(ChunkWitness, ChunkInfo, B256)> {
let version = Version::from(self.version);
let witness = self.build_guest_input(version);
let chunk_info = ChunkInfo::try_from(witness.clone()).map_err(|e| eyre::eyre!("{e}"))?;
assert_eq!(chunk_info.post_msg_queue_hash, self.post_msg_queue_hash);
let chunk_pi_hash = chunk_info.pi_hash_by_version(version);
Ok((witness, chunk_info, chunk_pi_hash))
}
/// 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,
@@ -165,14 +212,9 @@ impl ChunkProvingTask {
let err_parse_re = regex::Regex::new(pattern)?;
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}"))?;
let witness = self.build_guest_input(Version::euclid_v2());
match execute(archieved_witness) {
match execute(witness) {
Ok(_) => return Ok(()),
Err(e) => {
if let Some(caps) = err_parse_re.captures(&e) {

View File

@@ -1,5 +1,6 @@
use eyre::Result;
use sbv_primitives::{types::BlockWitness, Bytes, B256};
use sbv_core::BlockWitness;
use sbv_primitives::{types::consensus::TxL1Message, Bytes, B256};
/// An interpreter which is cirtical in translating chunk data
/// since we need to grep block witness and storage node data
@@ -12,13 +13,22 @@ pub trait ChunkInterpreter {
) -> Result<BlockWitness> {
Err(eyre::eyre!("no implement"))
}
fn try_fetch_storage_node(&self, _node_hash: B256) -> Result<Bytes> {
Err(eyre::eyre!("no implement"))
}
fn try_fetch_l1_msgs(&self, _block_number: u64) -> Result<Vec<TxL1Message>> {
Err(eyre::eyre!("no implement"))
}
}
pub trait TryFromWithInterpreter<T>: Sized {
fn try_from_with_interpret(value: T, intepreter: impl ChunkInterpreter) -> Result<Self>;
fn try_from_with_interpret(
value: T,
decryption_key: Option<&[u8]>,
intepreter: impl ChunkInterpreter,
) -> Result<Self>;
}
pub struct DummyInterpreter {}

View File

@@ -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 {
@@ -41,8 +41,11 @@ pub trait ProofVerifier {
#[derive(Debug, Serialize, Deserialize)]
pub struct CircuitConfig {
pub version: u8,
pub fork_name: String,
pub assets_path: String,
#[serde(default)]
pub features: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -50,7 +53,7 @@ pub struct VerifierConfig {
pub circuits: Vec<CircuitConfig>,
}
type HardForkName = String;
pub(crate) type HardForkName = String;
type VerifierType = Arc<Mutex<dyn ProofVerifier + Send>>;
static VERIFIERS: OnceLock<HashMap<HardForkName, VerifierType>> = OnceLock::new();
@@ -61,14 +64,18 @@ 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, cfg.version);
let ret = verifiers.insert(canonical_fork_name, Arc::new(Mutex::new(verifier)));
assert!(
ret.is_none(),
"DO NOT init the same fork {} twice",
cfg.fork_name
);
tracing::info!("load verifier config for fork {}", cfg.fork_name);
tracing::info!(
"load verifier config for fork {} (ver {})",
cfg.fork_name,
cfg.version
);
}
let ret = VERIFIERS.set(verifiers).is_ok();

View File

@@ -6,60 +6,52 @@ use crate::{
proofs::{AsRootProof, BatchProof, BundleProof, ChunkProof, IntoEvmProof},
utils::panic_catch,
};
use scroll_zkvm_types::public_inputs::ForkName;
use scroll_zkvm_verifier_euclid::verifier::UniversalVerifier;
use scroll_zkvm_types::version::Version;
use scroll_zkvm_verifier::verifier::UniversalVerifier;
use std::path::Path;
pub struct EuclidV2Verifier {
pub struct Verifier {
verifier: UniversalVerifier,
fork: ForkName,
version: Version,
}
impl EuclidV2Verifier {
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");
impl Verifier {
pub fn new(assets_dir: &str, ver_n: u8) -> Self {
let verifier_bin = Path::new(assets_dir);
Self {
verifier: UniversalVerifier::setup(&config, &exe, &verifier_bin)
.expect("Setting up chunk verifier"),
fork,
verifier: UniversalVerifier::setup(verifier_bin).expect("Setting up chunk verifier"),
version: Version::from(ver_n),
}
}
}
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.version));
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.version));
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.version));
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}"))
}

View File

@@ -152,18 +152,30 @@ pub unsafe extern "C" fn gen_universal_task(
fork_name: *const c_char,
expected_vk: *const u8,
expected_vk_len: usize,
decryption_key: *const u8,
decryption_key_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
let decryption_key = if decryption_key_len > 0 {
if decryption_key_len != 32 {
tracing::error!(
"gen_universal_task received {}-byte decryption key; expected 32",
decryption_key_len
);
return failed_handling_result();
}
Some(std::slice::from_raw_parts(
decryption_key,
decryption_key_len,
))
} else {
None
};
match libzkp::checkout_chunk_task(pre_task_str, decryption_key, cli) {
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();
}
@@ -178,13 +190,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);
@@ -248,6 +255,19 @@ pub unsafe extern "C" fn gen_wrapped_proof(
}
}
/// # Safety
#[no_mangle]
pub unsafe extern "C" fn univ_task_compatibility_fix(task_json: *const c_char) -> *mut c_char {
let task_json_str = c_char_to_str(task_json);
match libzkp::univ_task_compatibility_fix(task_json_str) {
Ok(result) => CString::new(result).unwrap().into_raw(),
Err(e) => {
tracing::error!("univ_task_compability_fix failed, error: {:#}", e);
std::ptr::null_mut()
}
}
}
/// # Safety
#[no_mangle]
pub unsafe extern "C" fn release_string(ptr: *mut c_char) {

View File

@@ -7,8 +7,9 @@ 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
libzkp = { path = "../libzkp"}
scroll-proving-sdk = { git = "https://github.com/scroll-tech/scroll-proving-sdk.git", rev = "05648db" }
serde.workspace = true
serde_json.workspace = true
once_cell.workspace =true
@@ -17,8 +18,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 +32,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"]

View File

@@ -0,0 +1,10 @@
{
"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/",
"7eb91f1885cc7a63cc848928f043fa56bf747161a74cd933d88c0456b90643346618ea25a7991845bbc5fd571670ee47379ba31ace92d345bca59702a0d4112d": "https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/0.6.0-rc.1/chunk/",
"dc653e7416628c612fa4d80b4724002bad4fde3653aef7316b80df0c19740a1bf2be2f4468d1ac4a70e7682babb1c60417e21c7633d4b55b58f44703ec82b05a": "https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/0.6.0-rc.1/batch/",
"14de1c74b663ed3c99acb03e90a5753b5923233c5c590864ad7746570297d16722c0e5294bcb1b3a9def836f8d0f18612a9860629b9497292976ca11844b7e73": "https://circuit-release.s3.us-west-2.amazonaws.com/scroll-zkvm/releases/0.6.0-rc.1/bundle/"
}
}

View File

@@ -34,11 +34,6 @@ struct Args {
#[derive(Subcommand, Debug)]
enum Commands {
/// Dump vk of this prover
Dump {
/// path to save the verifier's asset
asset_path: String,
},
Handle {
/// path to save the verifier's asset
task_path: String,
@@ -64,16 +59,10 @@ 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);

View File

@@ -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::{
@@ -12,16 +12,116 @@ use scroll_proving_sdk::{
ProvingService,
},
};
use scroll_zkvm_types::ProvingTask;
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,
@@ -44,8 +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,93 @@ 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 is_openvm_13 = prover_task.use_openvm_13;
let prover_task: ProvingTask = prover_task.into();
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,
is_openvm_13,
)?));
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,77 +326,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}"))?;
if !config.vks.is_empty() {
eyre::bail!("clean vks cache first or we will have wrong dumped vk");
}
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(())
}
}

View File

@@ -1,3 +1,5 @@
#![allow(dead_code)]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Serialize, Deserialize, Default)]

View File

@@ -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>;
}

View File

@@ -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")),
}
}
}

View File

@@ -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)?)
}
}

View File

@@ -0,0 +1,56 @@
use std::path::Path;
use super::CircuitsHandler;
use async_trait::async_trait;
use eyre::Result;
use libzkp::ProvingTaskExt;
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>, is_openvm_v13: bool) -> 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 << 21) - 100);
let config = ProverConfig {
path_app_config,
path_app_exe,
segment_len,
is_openvm_v13,
};
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<ProvingTaskExt> {
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)?)
}
}

View File

@@ -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.20251128092113-8629f088d78f
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

View File

@@ -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.20251128092113-8629f088d78f h1:j6SjP98MoWFFX9TwB1/nFYEkayqHQsrtE66Ll2C+oT0=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f/go.mod h1:Aa/kD1XB+OV/7rRxMQrjcPCB4b0pKyLH0gsTrtuHi38=
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=

View File

@@ -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=
@@ -792,6 +793,7 @@ github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS3
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0=
@@ -1199,6 +1201,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=
@@ -1409,11 +1412,15 @@ github.com/scroll-tech/da-codec v0.1.3-0.20250609113414-f33adf0904bd/go.mod h1:g
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/ecies-go/v2 v2.0.10-beta.1/go.mod h1:A+pHaITd+ogBm4Rk35xebF9OPiyMYlFlgqBOiY5PSjg=
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.20251128092359-25d5bf6b817b h1:pMQKnroJoS/FeL1aOWkz7/u1iBHUP8PWjZstNuzoUGE=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092359-25d5bf6b817b/go.mod h1:Aa/kD1XB+OV/7rRxMQrjcPCB4b0pKyLH0gsTrtuHi38=
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=
@@ -1611,6 +1618,7 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1632,6 +1640,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
@@ -1781,6 +1790,7 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
@@ -1789,6 +1799,7 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -15,7 +15,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

View File

@@ -66,17 +66,26 @@ func action(ctx *cli.Context) error {
registry := prometheus.DefaultRegisterer
observability.Server(ctx, db)
l1client, err := ethclient.Dial(cfg.L1Config.Endpoint)
// Init L1 connection
l1RpcClient, err := rpc.Dial(cfg.L1Config.Endpoint)
if err != nil {
log.Crit("failed to connect l1 geth", "config file", cfgFile, "error", err)
log.Crit("failed to dial raw RPC client to L1 endpoint", "endpoint", cfg.L1Config.Endpoint, "error", err)
}
l1client := ethclient.NewClient(l1RpcClient)
// sanity check config
if cfg.L1Config.RelayerConfig.GasOracleConfig.L1BaseFeeLimit == 0 || cfg.L1Config.RelayerConfig.GasOracleConfig.L1BlobBaseFeeLimit == 0 {
log.Crit("gas-oracle `l1_base_fee_limit` and `l1_blob_base_fee_limit` configs must be set")
}
l1watcher := watcher.NewL1WatcherClient(ctx.Context, l1client, cfg.L1Config.StartHeight, db, registry)
// Init watcher and relayer
l1watcher := watcher.NewL1WatcherClient(ctx.Context, l1RpcClient, cfg.L1Config.StartHeight, db, registry)
l1relayer, err := relayer.NewLayer1Relayer(ctx.Context, db, cfg.L1Config.RelayerConfig, relayer.ServiceTypeL1GasOracle, registry)
if err != nil {
log.Crit("failed to create new l1 relayer", "config file", cfgFile, "error", err)
}
// Start l1 watcher process
go utils.LoopWithContext(subCtx, 10*time.Second, func(ctx context.Context) {
// Fetch the latest block number to decrease the delay when fetching gas prices

View File

@@ -10,8 +10,8 @@ import (
"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"
"github.com/scroll-tech/go-ethereum/rpc"
"scroll-tech/common/database"
"scroll-tech/common/observability"
@@ -91,12 +91,13 @@ func action(ctx *cli.Context) error {
bundleProposer := watcher.NewBundleProposer(subCtx, cfg.L2Config.BundleProposerConfig, minCodecVersion, genesis.Config, db, registry)
// Init l2geth connection
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
l2client, err := rpc.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)
l2Watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress,
cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, cfg.L2Config.RelayerConfig.ValidiumMode, registry)
recovery := permissionless_batches.NewRecovery(subCtx, cfg, genesis, db, chunkProposer, batchProposer, bundleProposer, l2Watcher)

View File

@@ -12,6 +12,7 @@ import (
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rollup/l1"
"github.com/scroll-tech/go-ethereum/rpc"
"github.com/urfave/cli/v2"
"scroll-tech/common/database"
@@ -69,10 +70,11 @@ func action(ctx *cli.Context) error {
observability.Server(ctx, db)
// Init l2geth connection
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
l2client, err := rpc.Dial(cfg.L2Config.Endpoint)
if err != nil {
log.Crit("failed to connect l2 geth", "config file", cfgFile, "error", err)
}
l2ethClient := ethclient.NewClient(l2client)
genesisPath := ctx.String(utils.Genesis.Name)
genesis, err := utils.ReadGenesis(genesisPath)
@@ -96,8 +98,11 @@ 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)
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2ethClient, db, cfg.L2Config.RelayerConfig, genesis.Config, relayer.ServiceTypeL2RollupRelayer, registry)
if err != nil {
log.Crit("failed to create l2 relayer", "config file", cfgFile, "error", err)
}
@@ -111,7 +116,7 @@ func action(ctx *cli.Context) error {
batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, minCodecVersion, genesis.Config, db, cfg.L2Config.RelayerConfig.ValidiumMode, registry)
bundleProposer := watcher.NewBundleProposer(subCtx, cfg.L2Config.BundleProposerConfig, minCodecVersion, genesis.Config, db, registry)
l2watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, registry)
l2watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, cfg.L2Config.RelayerConfig.ValidiumMode, registry)
if cfg.RecoveryConfig != nil && cfg.RecoveryConfig.Enable {
log.Info("Starting rollup-relayer in recovery mode", "version", version.Version)
@@ -141,7 +146,7 @@ func action(ctx *cli.Context) error {
// 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)
number, loopErr := rutils.GetLatestConfirmedBlockNumber(ctx, l2ethClient, cfg.L2Config.Confirmations)
if loopErr != nil {
log.Error("failed to get block number", "err", loopErr)
return

View File

@@ -21,7 +21,9 @@
"check_committed_batches_window_minutes": 5,
"l1_base_fee_default": 15000000000,
"l1_blob_base_fee_default": 1,
"l1_blob_base_fee_threshold": 0
"l1_blob_base_fee_threshold": 0,
"l1_base_fee_limit": 20000000000,
"l1_blob_base_fee_limit": 20000000000
},
"gas_oracle_sender_signer_config": {
"signer_type": "PrivateKey",
@@ -49,13 +51,15 @@
"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,
"max_batches": 6,
"timeout": 7200,
"backlog_max": 75
"backlog_max": 75,
"blob_fee_tolerance": 500000000
},
"gas_oracle_config": {
"min_gas_price": 0,
@@ -92,7 +96,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

View File

@@ -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.20250825071838-cddc263e5ef6
github.com/scroll-tech/go-ethereum v1.10.14-0.20250626110859-cc9a1dd82de7
github.com/scroll-tech/da-codec v0.10.0
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f
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/deckarep/golang-set v1.8.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.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

View File

@@ -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=
@@ -86,13 +88,13 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
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.20250825071838-cddc263e5ef6 h1:EiLZgoRfGgF0QzZ1jGSlo60A6W7g9L/8XLWMhwwtKJ0=
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.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.10.0 h1:IPHxyTyXTWPV0Q+DZ08cod2fWkhUvrfysmj/VBpB+WU=
github.com/scroll-tech/da-codec v0.10.0/go.mod h1:MBlIP4wCXPcUDZ/Ci2B7n/2IbVU1WBo9OTFTZ5ffE0U=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f h1:j6SjP98MoWFFX9TwB1/nFYEkayqHQsrtE66Ll2C+oT0=
github.com/scroll-tech/go-ethereum v1.10.14-0.20251128092113-8629f088d78f/go.mod h1:Aa/kD1XB+OV/7rRxMQrjcPCB4b0pKyLH0gsTrtuHi38=
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=

View File

@@ -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"`

View File

@@ -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 {
@@ -40,6 +48,10 @@ type BatchSubmission struct {
TimeoutSec int64 `json:"timeout"`
// The maximum number of pending batches to keep in the backlog.
BacklogMax int64 `json:"backlog_max"`
// BlobFeeTolerance is the absolute tolerance (in wei) added to the target blob fee.
// If the current fee is below target + tolerance, we proceed with submission.
// This prevents skipping submission when the price difference is negligible.
BlobFeeTolerance uint64 `json:"blob_fee_tolerance"`
}
// ChainMonitor this config is used to get batch status from chain_monitor API.
@@ -101,6 +113,10 @@ type GasOracleConfig struct {
L1BaseFeeDefault uint64 `json:"l1_base_fee_default"`
L1BlobBaseFeeDefault uint64 `json:"l1_blob_base_fee_default"`
// Upper limit values for gas oracle updates
L1BaseFeeLimit uint64 `json:"l1_base_fee_limit"`
L1BlobBaseFeeLimit uint64 `json:"l1_blob_base_fee_limit"`
// L1BlobBaseFeeThreshold the threshold of L1 blob base fee to enter the default gas price mode
L1BlobBaseFeeThreshold uint64 `json:"l1_blob_base_fee_threshold"`
}

View File

@@ -167,7 +167,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er
Chunks: chunks,
}
case encoding.CodecV7, encoding.CodecV8:
case encoding.CodecV7, encoding.CodecV8, encoding.CodecV9, encoding.CodecV10:
encodingBatch = &encoding.Batch{
Index: dbBatch.Index,
ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash),
@@ -242,10 +242,12 @@ func (b *BlobUploader) GetFirstUnuploadedBatchByPlatform(ctx context.Context, st
break
}
if len(batch.CommitTxHash) == 0 {
log.Debug("got batch not committed for blob uploading", "batch_index", batchIndex, "platform", platform.String())
return nil, nil
}
// disable this check to upload blobs before it's committed. This is to
// alleviate the case nodes try to fetch the blob from s3 before its uploaded.
// if len(batch.CommitTxHash) == 0 {
// log.Debug("got batch not committed for blob uploading", "batch_index", batchIndex, "platform", platform.String())
// return nil, nil
// }
return batch, nil
}

View File

@@ -173,6 +173,18 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
} else if err != nil {
return
}
// Cap base fee update at the configured upper limit
if limit := r.cfg.GasOracleConfig.L1BaseFeeLimit; baseFee > limit {
log.Error("L1 base fee exceed max limit, set to max limit", "baseFee", baseFee, "maxLimit", limit)
r.metrics.rollupL1RelayerGasPriceOracleFeeOverLimitTotal.Inc()
baseFee = limit
}
// Cap blob base fee update at the configured upper limit
if limit := r.cfg.GasOracleConfig.L1BlobBaseFeeLimit; blobBaseFee > limit {
log.Error("L1 blob base fee exceed max limit, set to max limit", "blobBaseFee", blobBaseFee, "maxLimit", limit)
r.metrics.rollupL1RelayerGasPriceOracleFeeOverLimitTotal.Inc()
blobBaseFee = limit
}
data, err := r.l1GasOracleABI.Pack("setL1BaseFeeAndBlobBaseFee", new(big.Int).SetUint64(baseFee), new(big.Int).SetUint64(blobBaseFee))
if err != nil {
log.Error("Failed to pack setL1BaseFeeAndBlobBaseFee", "block.Hash", block.Hash, "block.Height", block.Number, "block.BaseFee", baseFee, "block.BlobBaseFee", blobBaseFee, "err", err)

View File

@@ -8,11 +8,12 @@ import (
)
type l1RelayerMetrics struct {
rollupL1RelayerGasPriceOraclerRunTotal prometheus.Counter
rollupL1RelayerLatestBaseFee prometheus.Gauge
rollupL1RelayerLatestBlobBaseFee prometheus.Gauge
rollupL1UpdateGasOracleConfirmedTotal prometheus.Counter
rollupL1UpdateGasOracleConfirmedFailedTotal prometheus.Counter
rollupL1RelayerGasPriceOraclerRunTotal prometheus.Counter
rollupL1RelayerLatestBaseFee prometheus.Gauge
rollupL1RelayerLatestBlobBaseFee prometheus.Gauge
rollupL1UpdateGasOracleConfirmedTotal prometheus.Counter
rollupL1UpdateGasOracleConfirmedFailedTotal prometheus.Counter
rollupL1RelayerGasPriceOracleFeeOverLimitTotal prometheus.Counter
}
var (
@@ -43,6 +44,10 @@ func initL1RelayerMetrics(reg prometheus.Registerer) *l1RelayerMetrics {
Name: "rollup_layer1_update_gas_oracle_confirmed_failed_total",
Help: "The total number of updating layer1 gas oracle confirmed failed",
}),
rollupL1RelayerGasPriceOracleFeeOverLimitTotal: promauto.With(reg).NewCounter(prometheus.CounterOpts{
Name: "rollup_layer1_gas_price_oracle_fee_over_limit_total",
Help: "The total number of times when a gas price oracle fee update went over the configured limit",
}),
}
})
return l1RelayerMetric

View File

@@ -345,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)
@@ -360,15 +368,6 @@ func (r *Layer2Relayer) ProcessPendingBatches() {
return
}
// if backlog outgrow max size, forcesubmit 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)
@@ -453,6 +452,7 @@ func (r *Layer2Relayer) ProcessPendingBatches() {
// The next call of ProcessPendingBatches will then start with the batch with the different codec version.
batchesToSubmitLen := len(batchesToSubmit)
if batchesToSubmitLen > 0 && batchesToSubmit[batchesToSubmitLen-1].Batch.CodecVersion != dbBatch.CodecVersion {
forceSubmit = true
break
}
@@ -489,7 +489,7 @@ func (r *Layer2Relayer) ProcessPendingBatches() {
codecVersion := encoding.CodecVersion(firstBatch.CodecVersion)
switch codecVersion {
case encoding.CodecV7, encoding.CodecV8:
case encoding.CodecV7, encoding.CodecV8, encoding.CodecV9, encoding.CodecV10:
if r.cfg.ValidiumMode {
if len(batchesToSubmit) != 1 {
log.Error("validium mode only supports committing one batch at a time", "codecVersion", codecVersion, "start index", firstBatch.Index, "end index", lastBatch.Index, "batches count", len(batchesToSubmit))
@@ -563,12 +563,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 {
@@ -738,7 +748,7 @@ func (r *Layer2Relayer) finalizeBundle(bundle *orm.Bundle, withProof bool) error
var calldata []byte
switch encoding.CodecVersion(bundle.CodecVersion) {
case encoding.CodecV7, encoding.CodecV8:
case encoding.CodecV7, encoding.CodecV8, encoding.CodecV9, encoding.CodecV10:
if r.cfg.ValidiumMode {
calldata, err = r.constructFinalizeBundlePayloadValidium(dbBatch, endChunk, aggProof)
if err != nil {
@@ -1040,7 +1050,15 @@ func (r *Layer2Relayer) constructCommitBatchPayloadValidium(batch *dbBatchWithCh
lastChunk := batch.Chunks[len(batch.Chunks)-1]
commitment := common.HexToHash(lastChunk.EndBlockHash)
version := encoding.CodecVersion(batch.Batch.CodecVersion)
var version uint8
if encoding.CodecVersion(batch.Batch.CodecVersion) == encoding.CodecV8 || encoding.CodecVersion(batch.Batch.CodecVersion) == encoding.CodecV9 || encoding.CodecVersion(batch.Batch.CodecVersion) == encoding.CodecV10 {
// Validium version line starts with v1,
// but rollup-relayer behavior follows v8.
version = 1
} else {
return nil, 0, 0, fmt.Errorf("unexpected codec version %d for validium mode", 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 {
return nil, 0, 0, fmt.Errorf("failed to pack commitBatch: %w", err)
@@ -1237,16 +1255,20 @@ func (r *Layer2Relayer) skipSubmitByFee(oldest time.Time, metrics *l2RelayerMetr
target := calculateTargetPrice(windowSec, r.batchStrategy, oldest, hist)
current := hist[len(hist)-1]
// apply absolute tolerance offset to target
tolerance := new(big.Int).SetUint64(r.cfg.BatchSubmission.BlobFeeTolerance)
threshold := new(big.Int).Add(target, tolerance)
currentFloat, _ := current.Float64()
targetFloat, _ := target.Float64()
metrics.rollupL2RelayerCurrentBlobPrice.Set(currentFloat)
metrics.rollupL2RelayerTargetBlobPrice.Set(targetFloat)
// if current fee > target and still inside the timeout window, skip
if current.Cmp(target) > 0 && time.Since(oldest) < time.Duration(windowSec)*time.Second {
// if current fee > threshold (target + tolerance) and still inside the timeout window, skip
if current.Cmp(threshold) > 0 && time.Since(oldest) < time.Duration(windowSec)*time.Second {
return true, fmt.Errorf(
"blob-fee above target & window not yet passed; current=%s target=%s age=%s",
current.String(), target.String(), time.Since(oldest),
"blob-fee above threshold & window not yet passed; current=%s target=%s threshold=%s tolerance=%s age=%s",
current.String(), target.String(), threshold.String(), tolerance.String(), time.Since(oldest),
)
}

View File

@@ -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)

View File

@@ -2,6 +2,7 @@ package sender
import (
"errors"
"fmt"
"math/big"
"github.com/scroll-tech/go-ethereum"
@@ -52,7 +53,7 @@ func (s *Sender) estimateDynamicGas(to *common.Address, data []byte, baseFee uin
if err != nil {
log.Error("estimateDynamicGas estimateGasLimit failure",
"from", s.transactionSigner.GetAddr().String(), "nonce", s.transactionSigner.GetNonce(), "to address", to.String(),
"fallback gas limit", "error", err)
"error", err)
return nil, err
}
@@ -118,7 +119,7 @@ func (s *Sender) estimateGasLimit(to *common.Address, data []byte, sidecar *type
gasLimitWithoutAccessList, err := s.client.EstimateGas(s.ctx, msg)
if err != nil {
log.Error("estimateGasLimit EstimateGas failure without access list", "error", err)
log.Error("estimateGasLimit EstimateGas failure without access list", "error", err, "msg", fmt.Sprintf("%+v", msg))
return 0, nil, err
}

View File

@@ -7,12 +7,13 @@ import (
"fmt"
"math/big"
"strings"
"sync"
"time"
"github.com/holiman/uint256"
"github.com/prometheus/client_golang/prometheus"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/consensus/misc"
"github.com/scroll-tech/go-ethereum/common/hexutil"
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
"github.com/scroll-tech/go-ethereum/ethclient"
@@ -66,8 +67,10 @@ type FeeData struct {
// 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.
rpcClient *rpc.Client // Raw RPC client
gethClient *gethclient.Client // Client to use for CreateAccessList
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 +93,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,12 +109,43 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c
return nil, fmt.Errorf("failed to create transaction signer, err: %w", err)
}
// 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,
rpcClient: rpcClient,
gethClient: gethclient.New(rpcClient),
client: client,
writeClients: writeClients,
chainID: chainID,
transactionSigner: transactionSigner,
db: db,
@@ -169,6 +204,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()
@@ -178,6 +289,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 {
@@ -194,21 +311,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)
@@ -230,7 +350,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)
@@ -515,6 +635,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)
@@ -528,7 +652,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
@@ -645,7 +769,7 @@ 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)
@@ -706,10 +830,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
@@ -719,13 +843,24 @@ func (s *Sender) getBlockNumberAndBaseFeeAndBlobFee(ctx context.Context) (uint64
var blobBaseFee uint64
if excess := header.ExcessBlobGas; excess != nil {
blobBaseFee = misc.CalcBlobFee(*excess).Uint64()
// Leave it up to the L1 node to compute the correct blob base fee.
// Previously we would compute it locally using `CalcBlobFee`, but
// that approach requires syncing any future L1 configuration changes.
// Note: The fetched blob base fee might not correspond to the block
// that we fetched in the previous step, but this is acceptable.
var blobBaseFeeHex hexutil.Big
if err := s.rpcClient.CallContext(ctx, &blobBaseFeeHex, "eth_blobBaseFee"); err != nil {
return 0, 0, 0, 0, fmt.Errorf("failed to call eth_blobBaseFee, err: %w", err)
}
// A correct L1 node could not return a value that overflows uint64
blobBaseFee = blobBaseFeeHex.ToInt().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")
}
@@ -742,23 +877,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
}

View File

@@ -21,6 +21,7 @@ import (
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
@@ -94,8 +95,9 @@ func setupEnv(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, migrate.ResetDB(sqlDB))
l1Client, err := testApps.GetPoSL1Client()
l1RawClient, err := testApps.GetPoSL1Client()
assert.NoError(t, err)
l1Client := ethclient.NewClient(l1RawClient)
chainID, err := l1Client.ChainID(context.Background())
assert.NoError(t, err)
@@ -291,7 +293,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 +334,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 +469,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 +801,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)

View File

@@ -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, &params.ChainConfig{
@@ -154,7 +153,6 @@ func testBatchProposerBlobSizeLimitCodecV7(t *testing.T) {
chainConfig := &params.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 := &params.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

View File

@@ -86,15 +86,19 @@ func testBundleProposerLimitsCodecV7(t *testing.T) {
_, err = batchOrm.InsertBatch(context.Background(), batch, encoding.CodecV0, utils.BatchMetrics{})
assert.NoError(t, err)
block3 := *block1
block3.Header = &gethTypes.Header{}
*block3.Header = *block1.Header
block3.Header.Number = new(big.Int).SetUint64(block2.Header.Number.Uint64() + 1)
l2BlockOrm := orm.NewL2Block(db)
err = l2BlockOrm.InsertL2Blocks(context.Background(), []*encoding.Block{block1, block2})
err = l2BlockOrm.InsertL2Blocks(context.Background(), []*encoding.Block{block1, block2, &block3})
assert.NoError(t, err)
chainConfig := &params.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: 1,
MaxL2GasPerChunk: math.MaxUint64,
MaxL2GasPerChunk: 1152994, // One block per chunk via gas limit
ChunkTimeoutSec: math.MaxUint32,
MaxUncompressedBatchBytesSize: math.MaxUint64,
}, encoding.CodecV7, chainConfig, db, nil)

View File

@@ -54,7 +54,6 @@ type ChunkProposer struct {
// NewChunkProposer creates a new ChunkProposer instance.
func NewChunkProposer(ctx context.Context, cfg *config.ChunkProposerConfig, minCodecVersion encoding.CodecVersion, chainCfg *params.ChainConfig, db *gorm.DB, reg prometheus.Registerer) *ChunkProposer {
log.Info("new chunk proposer",
"maxBlockNumPerChunk", cfg.MaxBlockNumPerChunk,
"maxL2GasPerChunk", cfg.MaxL2GasPerChunk,
"chunkTimeoutSec", cfg.ChunkTimeoutSec,
"maxBlobSize", maxBlobSize)
@@ -232,10 +231,9 @@ func (p *ChunkProposer) ProposeChunk() error {
return err
}
maxBlocksThisChunk := p.cfg.MaxBlockNumPerChunk
// select at most maxBlocksThisChunk blocks
blocks, err := p.l2BlockOrm.GetL2BlocksGEHeight(p.ctx, unchunkedBlockHeight, int(maxBlocksThisChunk))
// select blocks without a hard limit on count in practice (use a large value)
// The actual limits will be enforced by gas, timeout, and blob size constraints
blocks, err := p.l2BlockOrm.GetL2BlocksGEHeight(p.ctx, unchunkedBlockHeight, 1000)
if err != nil {
return err
}
@@ -247,11 +245,13 @@ func (p *ChunkProposer) ProposeChunk() error {
// Ensure all blocks in the same chunk use the same hardfork name
// If a different hardfork name is found, truncate the blocks slice at that point
hardforkName := encoding.GetHardforkName(p.chainCfg, blocks[0].Header.Number.Uint64(), blocks[0].Header.Time)
hardforkBoundary := false
for i := 1; i < len(blocks); i++ {
currentHardfork := encoding.GetHardforkName(p.chainCfg, blocks[i].Header.Number.Uint64(), blocks[i].Header.Time)
if currentHardfork != hardforkName {
// Truncate blocks at hardfork boundary
blocks = blocks[:i]
maxBlocksThisChunk = uint64(i) // update maxBlocksThisChunk to trigger chunking, because these blocks are the last blocks before the hardfork
hardforkBoundary = true
break
}
}
@@ -323,9 +323,22 @@ func (p *ChunkProposer) ProposeChunk() error {
return fmt.Errorf("failed to calculate chunk metrics: %w", calcErr)
}
// No breaking condition met, but hardfork boundary reached
if hardforkBoundary {
log.Info("hardfork boundary reached, proposing chunk",
"block count", len(chunk.Blocks),
"codec version", codecVersion,
"start block number", chunk.Blocks[0].Header.Number,
"end block number", chunk.Blocks[len(chunk.Blocks)-1].Header.Number)
p.recordAllChunkMetrics(metrics)
return p.updateDBChunkInfo(&chunk, codecVersion, metrics)
}
// No breaking condition met, check for timeout
currentTimeSec := uint64(time.Now().Unix())
if metrics.FirstBlockTimestamp+p.cfg.ChunkTimeoutSec < currentTimeSec || metrics.NumBlocks == maxBlocksThisChunk {
log.Info("reached maximum number of blocks in chunk or first block timeout",
if metrics.FirstBlockTimestamp+p.cfg.ChunkTimeoutSec < currentTimeSec {
log.Info("first block timeout reached",
"block count", len(chunk.Blocks),
"start block number", chunk.Blocks[0].Header.Number,
"start block timestamp", metrics.FirstBlockTimestamp,

View File

@@ -19,25 +19,25 @@ import (
"scroll-tech/rollup/internal/utils"
)
func newUint64(val uint64) *uint64 { return &val }
func testChunkProposerLimitsCodecV7(t *testing.T) {
tests := []struct {
name string
maxBlockNum uint64
maxL2Gas uint64
chunkTimeoutSec uint64
expectedChunksLen int
expectedBlocksInFirstChunk int // only be checked when expectedChunksLen > 0
GalileoTime *uint64
}{
{
name: "NoLimitReached",
maxBlockNum: 100,
maxL2Gas: 20_000_000,
chunkTimeoutSec: 1000000000000,
expectedChunksLen: 0,
},
{
name: "Timeout",
maxBlockNum: 100,
maxL2Gas: 20_000_000,
chunkTimeoutSec: 0,
expectedChunksLen: 1,
@@ -45,15 +45,13 @@ func testChunkProposerLimitsCodecV7(t *testing.T) {
},
{
name: "MaxL2GasPerChunkIs0",
maxBlockNum: 10,
maxL2Gas: 0,
chunkTimeoutSec: 1000000000000,
expectedChunksLen: 0,
},
{
name: "MaxBlockNumPerChunkIs1",
maxBlockNum: 1,
maxL2Gas: 20_000_000,
name: "SingleBlockByGasLimit",
maxL2Gas: 1_100_000,
chunkTimeoutSec: 1000000000000,
expectedChunksLen: 1,
expectedBlocksInFirstChunk: 1,
@@ -62,12 +60,19 @@ func testChunkProposerLimitsCodecV7(t *testing.T) {
// In this test the second block is not included in the chunk because together
// with the first block it exceeds the maxL2GasPerChunk limit.
name: "MaxL2GasPerChunkIsSecondBlock",
maxBlockNum: 10,
maxL2Gas: 1_153_000,
chunkTimeoutSec: 1000000000000,
expectedChunksLen: 1,
expectedBlocksInFirstChunk: 1,
},
{
name: "SingleBlockByForkBoundary",
maxL2Gas: 20_000_000,
chunkTimeoutSec: 1000000000000,
expectedChunksLen: 1,
expectedBlocksInFirstChunk: 1,
GalileoTime: newUint64(1669364525), // timestamp of `block2`
},
}
for _, tt := range tests {
@@ -84,12 +89,26 @@ func testChunkProposerLimitsCodecV7(t *testing.T) {
_, err = chunkOrm.InsertChunk(context.Background(), &encoding.Chunk{Blocks: []*encoding.Block{{Header: &gethTypes.Header{Number: big.NewInt(0)}}}}, encoding.CodecV0, utils.ChunkMetrics{})
assert.NoError(t, err)
// Initialize the chunk proposer.
chainConfig := &params.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),
FeynmanTime: new(uint64),
GalileoTime: tt.GalileoTime,
}
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
MaxBlockNumPerChunk: tt.maxBlockNum,
MaxL2GasPerChunk: tt.maxL2Gas,
ChunkTimeoutSec: tt.chunkTimeoutSec,
MaxUncompressedBatchBytesSize: math.MaxUint64,
}, encoding.CodecV7, &params.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)}, db, nil)
}, encoding.CodecV7, chainConfig, db, nil)
// Run one round of chunk proposing.
cp.TryProposeChunk()
chunks, err := chunkOrm.GetChunksGEIndex(context.Background(), 1, 0)
@@ -110,53 +129,6 @@ func testChunkProposerLimitsCodecV7(t *testing.T) {
}
}
func testChunkProposerBlobSizeLimitCodecV7(t *testing.T) {
db := setupDB(t)
defer database.CloseDB(db)
block := readBlockFromJSON(t, "../../../testdata/blockTrace_03.json")
for i := uint64(0); i < 510; i++ {
l2BlockOrm := orm.NewL2Block(db)
block.Header.Number = new(big.Int).SetUint64(i + 1)
block.Header.Time = i + 1
err := l2BlockOrm.InsertL2Blocks(context.Background(), []*encoding.Block{block})
assert.NoError(t, err)
}
// Add genesis chunk.
chunkOrm := orm.NewChunk(db)
_, err := chunkOrm.InsertChunk(context.Background(), &encoding.Chunk{Blocks: []*encoding.Block{{Header: &gethTypes.Header{Number: big.NewInt(0)}}}}, encoding.CodecV0, utils.ChunkMetrics{})
assert.NoError(t, err)
chainConfig := &params.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: 255,
MaxL2GasPerChunk: math.MaxUint64,
ChunkTimeoutSec: math.MaxUint32,
MaxUncompressedBatchBytesSize: math.MaxUint64,
}, encoding.CodecV7, chainConfig, db, nil)
for i := 0; i < 2; i++ {
cp.TryProposeChunk()
}
chunkOrm = orm.NewChunk(db)
chunks, err := chunkOrm.GetChunksGEIndex(context.Background(), 1, 0)
assert.NoError(t, err)
var expectedNumChunks int = 2
var numBlocksMultiplier uint64 = 255
assert.Len(t, chunks, expectedNumChunks)
for i, chunk := range chunks {
expected := numBlocksMultiplier * (uint64(i) + 1)
if expected > 2000 {
expected = 2000
}
assert.Equal(t, expected, chunk.EndBlockNumber)
}
}
func testChunkProposerUncompressedBatchBytesLimitCodecV8(t *testing.T) {
db := setupDB(t)
defer database.CloseDB(db)
@@ -204,7 +176,6 @@ func testChunkProposerUncompressedBatchBytesLimitCodecV8(t *testing.T) {
// Set max_uncompressed_batch_bytes_size to 4KiB (4 * 1024)
// One block (~3KiB) should fit, but two blocks (~6KiB) should exceed the limit
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
MaxBlockNumPerChunk: math.MaxUint64, // No block number limit
MaxL2GasPerChunk: math.MaxUint64, // No gas limit
ChunkTimeoutSec: math.MaxUint32, // No timeout limit
MaxUncompressedBatchBytesSize: 4 * 1024, // 4KiB limit

View File

@@ -3,13 +3,15 @@ package watcher
import (
"context"
"errors"
"fmt"
"math/big"
"github.com/prometheus/client_golang/prometheus"
"github.com/scroll-tech/go-ethereum/consensus/misc"
"github.com/scroll-tech/go-ethereum/common/hexutil"
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rpc"
"gorm.io/gorm"
"scroll-tech/common/types"
@@ -20,7 +22,8 @@ import (
// L1WatcherClient will listen for smart contract events from Eth L1.
type L1WatcherClient struct {
ctx context.Context
client *ethclient.Client
rpcClient *rpc.Client // Raw RPC client
client *ethclient.Client // Go SDK RPC client
l1BlockOrm *orm.L1Block
// The height of the block that the watcher has retrieved header rlp
@@ -30,7 +33,7 @@ type L1WatcherClient struct {
}
// NewL1WatcherClient returns a new instance of L1WatcherClient.
func NewL1WatcherClient(ctx context.Context, client *ethclient.Client, startHeight uint64, db *gorm.DB, reg prometheus.Registerer) *L1WatcherClient {
func NewL1WatcherClient(ctx context.Context, rpcClient *rpc.Client, startHeight uint64, db *gorm.DB, reg prometheus.Registerer) *L1WatcherClient {
l1BlockOrm := orm.NewL1Block(db)
savedL1BlockHeight, err := l1BlockOrm.GetLatestL1BlockHeight(ctx)
if err != nil {
@@ -43,7 +46,8 @@ func NewL1WatcherClient(ctx context.Context, client *ethclient.Client, startHeig
return &L1WatcherClient{
ctx: ctx,
client: client,
rpcClient: rpcClient,
client: ethclient.NewClient(rpcClient),
l1BlockOrm: l1BlockOrm,
processedBlockHeight: savedL1BlockHeight,
@@ -80,7 +84,17 @@ func (w *L1WatcherClient) FetchBlockHeader(blockHeight uint64) error {
var blobBaseFee uint64
if excess := block.ExcessBlobGas; excess != nil {
blobBaseFee = misc.CalcBlobFee(*excess).Uint64()
// Leave it up to the L1 node to compute the correct blob base fee.
// Previously we would compute it locally using `CalcBlobFee`, but
// that approach requires syncing any future L1 configuration changes.
// Note: The fetched blob base fee might not correspond to the block
// that we fetched in the previous step, but this is acceptable.
var blobBaseFeeHex hexutil.Big
if err := w.rpcClient.CallContext(w.ctx, &blobBaseFeeHex, "eth_blobBaseFee"); err != nil {
return fmt.Errorf("failed to call eth_blobBaseFee, err: %w", err)
}
// A correct L1 node could not return a value that overflows uint64
blobBaseFee = blobBaseFeeHex.ToInt().Uint64()
}
l1Block := orm.L1Block{

View File

@@ -21,10 +21,10 @@ import (
func setupL1Watcher(t *testing.T) (*L1WatcherClient, *gorm.DB) {
db := setupDB(t)
client, err := testApps.GetPoSL1Client()
l1RawClient, err := testApps.GetPoSL1Client()
assert.NoError(t, err)
l1Cfg := cfg.L1Config
watcher := NewL1WatcherClient(context.Background(), client, l1Cfg.StartHeight, db, nil)
watcher := NewL1WatcherClient(context.Background(), l1RawClient, l1Cfg.StartHeight, db, nil)
return watcher, db
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/scroll-tech/da-codec/encoding"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/event"
"github.com/scroll-tech/go-ethereum/log"
@@ -24,6 +25,7 @@ type L2WatcherClient struct {
event.Feed
*ethclient.Client
rpcCli *rpc.Client
l2BlockOrm *orm.L2Block
@@ -32,16 +34,19 @@ type L2WatcherClient struct {
messageQueueAddress common.Address
withdrawTrieRootSlot common.Hash
validiumMode bool
metrics *l2WatcherMetrics
chainCfg *params.ChainConfig
}
// NewL2WatcherClient take a l2geth instance to generate a l2watcherclient instance
func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmations rpc.BlockNumber, messageQueueAddress common.Address, withdrawTrieRootSlot common.Hash, chainCfg *params.ChainConfig, db *gorm.DB, reg prometheus.Registerer) *L2WatcherClient {
func NewL2WatcherClient(ctx context.Context, client *rpc.Client, confirmations rpc.BlockNumber, messageQueueAddress common.Address, withdrawTrieRootSlot common.Hash, chainCfg *params.ChainConfig, db *gorm.DB, validiumMode bool, reg prometheus.Registerer) *L2WatcherClient {
return &L2WatcherClient{
ctx: ctx,
Client: client,
Client: ethclient.NewClient(client),
rpcCli: client,
l2BlockOrm: orm.NewL2Block(db),
@@ -50,6 +55,8 @@ func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmat
messageQueueAddress: messageQueueAddress,
withdrawTrieRootSlot: withdrawTrieRootSlot,
validiumMode: validiumMode,
metrics: initL2WatcherMetrics(reg),
chainCfg: chainCfg,
@@ -95,21 +102,51 @@ func (w *L2WatcherClient) GetAndStoreBlocks(ctx context.Context, from, to uint64
return fmt.Errorf("failed to BlockByNumber: %v. number: %v", err, number)
}
blockTxs := block.Transactions()
var count int
for _, tx := range block.Transactions() {
for _, tx := range blockTxs {
if tx.IsL1MessageTx() {
count++
}
}
log.Info("retrieved block", "height", block.Header().Number, "hash", block.Header().Hash().String(), "L1 message count", count)
// use original (encrypted) L1 message txs in validium mode
if w.validiumMode {
var txs []*types.Transaction
if count > 0 {
log.Info("Fetching encrypted messages in validium mode")
err = w.rpcCli.CallContext(ctx, &txs, "scroll_getL1MessagesInBlock", block.Hash(), "synced")
if err != nil {
return fmt.Errorf("failed to get L1 messages: %v, block hash: %v", err, block.Hash().Hex())
}
}
// sanity check
if len(txs) != count {
return fmt.Errorf("L1 message count mismatch: expected %d, got %d", count, len(txs))
}
for ii := 0; ii < count; ii++ {
// sanity check
if blockTxs[ii].AsL1MessageTx().QueueIndex != txs[ii].AsL1MessageTx().QueueIndex {
return fmt.Errorf("L1 message queue index mismatch at index %d: expected %d, got %d", ii, blockTxs[ii].AsL1MessageTx().QueueIndex, txs[ii].AsL1MessageTx().QueueIndex)
}
log.Info("Replacing L1 message tx in validium mode", "index", ii, "queueIndex", txs[ii].AsL1MessageTx().QueueIndex, "decryptedTxHash", blockTxs[ii].Hash().Hex(), "originalTxHash", txs[ii].Hash().Hex())
blockTxs[ii] = txs[ii]
}
}
withdrawRoot, err3 := w.StorageAt(ctx, w.messageQueueAddress, w.withdrawTrieRootSlot, big.NewInt(int64(number)))
if err3 != nil {
return fmt.Errorf("failed to get withdrawRoot: %v. number: %v", err3, number)
}
blocks = append(blocks, &encoding.Block{
Header: block.Header(),
Transactions: encoding.TxsToTxsData(block.Transactions()),
Transactions: encoding.TxsToTxsData(blockTxs),
WithdrawRoot: common.BytesToHash(withdrawRoot),
})
}

View File

@@ -2,12 +2,13 @@ package watcher
import (
"context"
"os"
"testing"
"gorm.io/gorm"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
@@ -20,7 +21,7 @@ import (
func setupL2Watcher(t *testing.T) (*L2WatcherClient, *gorm.DB) {
db := setupDB(t)
l2cfg := cfg.L2Config
watcher := NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessageQueueAddress, l2cfg.WithdrawTrieRootSlot, nil, db, nil)
watcher := NewL2WatcherClient(context.Background(), l2Rpc, l2cfg.Confirmations, l2cfg.L2MessageQueueAddress, l2cfg.WithdrawTrieRootSlot, nil, db, false, nil)
return watcher, db
}
@@ -34,7 +35,7 @@ func testFetchRunningMissingBlocks(t *testing.T) {
if err != nil {
return false
}
wc := prepareWatcherClient(l2Cli, db)
wc := prepareWatcherClient(l2Rpc, db)
wc.TryFetchRunningMissingBlocks(latestHeight)
fetchedHeight, err := l2BlockOrm.GetL2BlocksLatestHeight(context.Background())
return err == nil && fetchedHeight == latestHeight
@@ -42,7 +43,32 @@ func testFetchRunningMissingBlocks(t *testing.T) {
assert.True(t, ok)
}
func prepareWatcherClient(l2Cli *ethclient.Client, db *gorm.DB) *L2WatcherClient {
func prepareWatcherClient(l2Cli *rpc.Client, db *gorm.DB) *L2WatcherClient {
confirmations := rpc.LatestBlockNumber
return NewL2WatcherClient(context.Background(), l2Cli, confirmations, common.Address{}, common.Hash{}, nil, db, nil)
return NewL2WatcherClient(context.Background(), l2Cli, confirmations, common.Address{}, common.Hash{}, nil, db, false, nil)
}
// New test for raw RPC GetBlockByHash from an endpoint URL in env.
func TestRawRPCGetBlockByHash(t *testing.T) {
url := os.Getenv("RPC_ENDPOINT_URL")
if url == "" {
t.Log("warn: RPC_ENDPOINT_URL not set, skipping raw RPC test")
t.Skip("missing RPC_ENDPOINT_URL")
}
ctx := context.Background()
cli, err := rpc.DialContext(ctx, url)
if err != nil {
t.Fatalf("failed to dial RPC endpoint %s: %v", url, err)
}
defer cli.Close()
var txs []*types.Transaction
blkHash := common.HexToHash("0xc80cf12883341827d71c08f734ba9a9d6da7e59eb16921d26e6706887e552c74")
err = cli.CallContext(ctx, &txs, "scroll_getL1MessagesInBlock", blkHash, "synced")
if err != nil {
t.Logf("scroll_getL1MessagesInBlock failed: err=%v", err)
t.Fail()
}
t.Log(txs, txs[0].Hash())
}

Some files were not shown because too many files have changed in this diff Show More