mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-12 07:28:08 -05:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb6428848f | ||
|
|
df97200a41 | ||
|
|
c8146ebb1a | ||
|
|
5390ec93b4 | ||
|
|
3ce671098a | ||
|
|
7e9fb0c667 | ||
|
|
d7d09c2ac7 | ||
|
|
86175a04c3 | ||
|
|
dbb26f91b4 | ||
|
|
65620464fb | ||
|
|
bd9fdf885f | ||
|
|
7a1992ba91 | ||
|
|
8d667f9353 | ||
|
|
57a058c516 | ||
|
|
55612a0dbb | ||
|
|
a8b2706752 | ||
|
|
d9ae117548 | ||
|
|
de2669da2b | ||
|
|
b1d7654970 | ||
|
|
a6164046e1 | ||
|
|
119e62d4b1 | ||
|
|
16e0cbf542 | ||
|
|
bfaf2fd0e2 | ||
|
|
ea227b5c85 | ||
|
|
86b67f6dad | ||
|
|
711d17d1e8 | ||
|
|
87c81c6555 | ||
|
|
7118746e34 | ||
|
|
f180a40ed9 | ||
|
|
fc577a535e | ||
|
|
8b018327f2 | ||
|
|
98cb624951 | ||
|
|
9dba3f8f41 | ||
|
|
fb44382297 | ||
|
|
2a745ad7a9 | ||
|
|
7270a2a9bc | ||
|
|
20fafd9ca4 | ||
|
|
870565a18f | ||
|
|
0319b11a57 | ||
|
|
6d4374a5cc | ||
|
|
7ca897d887 | ||
|
|
f414523045 | ||
|
|
99ba4acdc0 | ||
|
|
9caa3d491e | ||
|
|
81589c55f5 | ||
|
|
d234156475 | ||
|
|
62f7cbad46 | ||
|
|
186e76852b | ||
|
|
3b045817dd | ||
|
|
e7dc628563 | ||
|
|
d743f2ce96 | ||
|
|
798179ee6d | ||
|
|
b706cb69d3 | ||
|
|
aa24cdd1db | ||
|
|
4398a36ee2 | ||
|
|
9a27499c03 |
14
.github/pull_request_template.md
vendored
14
.github/pull_request_template.md
vendored
@@ -1,9 +1,9 @@
|
||||
## 1. Purpose or design rationale of this PR
|
||||
### Purpose or design rationale of this PR
|
||||
|
||||
...
|
||||
*Describe your change. Make sure to answer these three questions: What does this PR do? Why does it do it? How does it do it?*
|
||||
|
||||
|
||||
## 2. PR title
|
||||
### PR title
|
||||
|
||||
Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) (as we are doing squash merge for each PR), so it must start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type):
|
||||
|
||||
@@ -18,17 +18,17 @@ Your PR title must follow [conventional commits](https://www.conventionalcommits
|
||||
- [ ] test: Adding missing tests or correcting existing tests
|
||||
|
||||
|
||||
## 3. Deployment tag versioning
|
||||
### Deployment tag versioning
|
||||
|
||||
Has `tag` in `common/version.go` been updated?
|
||||
|
||||
- [ ] This PR doesn't involve a new deployment, git tag, docker image tag
|
||||
- [ ] No, this PR doesn't involve a new deployment, git tag, docker image tag
|
||||
- [ ] Yes
|
||||
|
||||
|
||||
## 4. Breaking change label
|
||||
### Breaking change label
|
||||
|
||||
Does this PR have the `breaking-change` label?
|
||||
|
||||
- [ ] This PR is not a breaking change
|
||||
- [ ] No, this PR is not a breaking change
|
||||
- [ ] Yes
|
||||
|
||||
96
.github/workflows/bridge.yml
vendored
96
.github/workflows/bridge.yml
vendored
@@ -9,6 +9,8 @@ on:
|
||||
- alpha
|
||||
paths:
|
||||
- 'bridge/**'
|
||||
- 'common/**'
|
||||
- 'database/**'
|
||||
- '.github/workflows/bridge.yml'
|
||||
pull_request:
|
||||
types:
|
||||
@@ -18,14 +20,61 @@ on:
|
||||
- ready_for_review
|
||||
paths:
|
||||
- 'bridge/**'
|
||||
- 'common/**'
|
||||
- 'database/**'
|
||||
- '.github/workflows/bridge.yml'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: 'bridge'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Solc
|
||||
uses: supplypike/setup-bin@v3
|
||||
with:
|
||||
uri: 'https://github.com/ethereum/solidity/releases/download/v0.8.16/solc-static-linux'
|
||||
name: 'solc'
|
||||
version: '0.8.16'
|
||||
- name: Install Geth Tools
|
||||
uses: gacts/install-geth-tools@v1
|
||||
- name: Lint
|
||||
working-directory: 'bridge'
|
||||
run: |
|
||||
rm -rf $HOME/.cache/golangci-lint
|
||||
make mock_abi
|
||||
make lint
|
||||
goimports-lint:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install goimports
|
||||
run: go install golang.org/x/tools/cmd/goimports
|
||||
- name: Run goimports lint
|
||||
run: goimports -local scroll-tech/bridge/ -w .
|
||||
working-directory: 'bridge'
|
||||
- name: Run go mod tidy
|
||||
run: go mod tidy
|
||||
working-directory: 'bridge'
|
||||
# If there are any diffs from goimports or go mod tidy, fail.
|
||||
- name: Verify no changes from goimports and go mod tidy
|
||||
working-directory: 'bridge'
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
tests:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -43,31 +92,24 @@ jobs:
|
||||
version: '0.8.16'
|
||||
- name: Install Geth Tools
|
||||
uses: gacts/install-geth-tools@v1
|
||||
- name: Lint
|
||||
- name: Build prerequisites
|
||||
run: |
|
||||
rm -rf $HOME/.cache/golangci-lint
|
||||
make mock_abi
|
||||
make lint
|
||||
goimports-lint:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
make dev_docker
|
||||
make -C bridge mock_abi
|
||||
- name: Build bridge binaries
|
||||
working-directory: 'bridge'
|
||||
run: |
|
||||
make bridge_bins
|
||||
- name: Test bridge packages
|
||||
working-directory: 'bridge'
|
||||
run: |
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install goimports
|
||||
run: go install golang.org/x/tools/cmd/goimports
|
||||
- run: goimports -local scroll-tech/bridge/ -w .
|
||||
- run: go mod tidy
|
||||
# If there are any diffs from goimports or go mod tidy, fail.
|
||||
- name: Verify no changes from goimports and go mod tidy
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
flags: bridge
|
||||
# docker-build:
|
||||
# if: github.event.pull_request.draft == false
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
15
.github/workflows/bridge_history_api.yml
vendored
15
.github/workflows/bridge_history_api.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: BridgeHistoryApi
|
||||
name: BridgeHistoryAPI
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
# - name: Install Go
|
||||
# uses: actions/setup-go@v2
|
||||
# with:
|
||||
# go-version: 1.20.x
|
||||
# go-version: 1.19.x
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v2
|
||||
# - name: Lint
|
||||
@@ -46,13 +46,19 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Test
|
||||
run: |
|
||||
go get ./...
|
||||
make test
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: bridge-history-api
|
||||
goimports-lint:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
@@ -60,7 +66,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install goimports
|
||||
@@ -73,4 +79,3 @@ jobs:
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
49
.github/workflows/common.yml
vendored
49
.github/workflows/common.yml
vendored
@@ -20,10 +20,6 @@ on:
|
||||
- 'common/**'
|
||||
- '.github/workflows/common.yml'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: 'common'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
if: github.event.pull_request.draft == false
|
||||
@@ -37,7 +33,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Cache cargo
|
||||
@@ -45,6 +41,7 @@ jobs:
|
||||
with:
|
||||
workspaces: "common/libzkp/impl -> target"
|
||||
- name: Lint
|
||||
working-directory: 'common'
|
||||
run: |
|
||||
rm -rf $HOME/.cache/golangci-lint
|
||||
make lint
|
||||
@@ -55,16 +52,52 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install goimports
|
||||
run: go install golang.org/x/tools/cmd/goimports
|
||||
- run: goimports -local scroll-tech/common/ -w .
|
||||
- run: go mod tidy
|
||||
- name: Run goimports lint
|
||||
working-directory: 'common'
|
||||
run: goimports -local scroll-tech/common/ -w .
|
||||
- name: Run go mod tidy
|
||||
working-directory: 'common'
|
||||
run: go mod tidy
|
||||
# If there are any diffs from goimports or go mod tidy, fail.
|
||||
- name: Verify no changes from goimports and go mod tidy
|
||||
working-directory: 'common'
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
tests:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Solc
|
||||
uses: supplypike/setup-bin@v3
|
||||
with:
|
||||
uri: 'https://github.com/ethereum/solidity/releases/download/v0.8.16/solc-static-linux'
|
||||
name: 'solc'
|
||||
version: '0.8.16'
|
||||
- name: Install Geth Tools
|
||||
uses: gacts/install-geth-tools@v1
|
||||
- name: Build prerequisites
|
||||
run: |
|
||||
make dev_docker
|
||||
- name: Test common packages
|
||||
working-directory: 'common'
|
||||
run: |
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: common
|
||||
|
||||
54
.github/workflows/coordinator.yml
vendored
54
.github/workflows/coordinator.yml
vendored
@@ -9,6 +9,8 @@ on:
|
||||
- alpha
|
||||
paths:
|
||||
- 'coordinator/**'
|
||||
- 'common/**'
|
||||
- 'database/**'
|
||||
- '.github/workflows/coordinator.yml'
|
||||
pull_request:
|
||||
types:
|
||||
@@ -18,12 +20,10 @@ on:
|
||||
- ready_for_review
|
||||
paths:
|
||||
- 'coordinator/**'
|
||||
- 'common/**'
|
||||
- 'database/**'
|
||||
- '.github/workflows/coordinator.yml'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: 'coordinator'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
if: github.event.pull_request.draft == false
|
||||
@@ -37,10 +37,11 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Lint
|
||||
working-directory: 'coordinator'
|
||||
run: |
|
||||
rm -rf $HOME/.cache/golangci-lint
|
||||
make lint
|
||||
@@ -51,15 +52,20 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install goimports
|
||||
run: go install golang.org/x/tools/cmd/goimports
|
||||
- run: goimports -local scroll-tech/coordinator/ -w .
|
||||
- run: go mod tidy
|
||||
- name: Run goimports lint
|
||||
working-directory: 'coordinator'
|
||||
run: goimports -local scroll-tech/coordinator/ -w .
|
||||
- name: Run go mod tidy
|
||||
working-directory: 'coordinator'
|
||||
run: go mod tidy
|
||||
# If there are any diffs from goimports or go mod tidy, fail.
|
||||
- name: Verify no changes from goimports and go mod tidy
|
||||
working-directory: 'coordinator'
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
exit 1
|
||||
@@ -80,3 +86,35 @@ jobs:
|
||||
# push: false
|
||||
# # cache-from: type=gha,scope=${{ github.workflow }}
|
||||
# # cache-to: type=gha,scope=${{ github.workflow }}
|
||||
tests:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Solc
|
||||
uses: supplypike/setup-bin@v3
|
||||
with:
|
||||
uri: 'https://github.com/ethereum/solidity/releases/download/v0.8.16/solc-static-linux'
|
||||
name: 'solc'
|
||||
version: '0.8.16'
|
||||
- name: Install Geth Tools
|
||||
uses: gacts/install-geth-tools@v1
|
||||
- name: Build prerequisites
|
||||
run: |
|
||||
make dev_docker
|
||||
- name: Test coordinator packages
|
||||
working-directory: 'coordinator'
|
||||
run: |
|
||||
# go test -exec "env LD_LIBRARY_PATH=${PWD}/verifier/lib" -v -race -gcflags="-l" -ldflags="-s=false" -coverpkg="scroll-tech/coordinator" -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic -tags mock_verifier ./...
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: coordinator
|
||||
|
||||
51
.github/workflows/database.yml
vendored
51
.github/workflows/database.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
- alpha
|
||||
paths:
|
||||
- 'database/**'
|
||||
- 'common/**'
|
||||
- '.github/workflows/database.yml'
|
||||
pull_request:
|
||||
types:
|
||||
@@ -18,12 +19,9 @@ on:
|
||||
- ready_for_review
|
||||
paths:
|
||||
- 'database/**'
|
||||
- 'common/**'
|
||||
- '.github/workflows/database.yml'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: 'database'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
if: github.event.pull_request.draft == false
|
||||
@@ -32,10 +30,11 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Lint
|
||||
working-directory: 'database'
|
||||
run: |
|
||||
rm -rf $HOME/.cache/golangci-lint
|
||||
make lint
|
||||
@@ -46,16 +45,52 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install goimports
|
||||
run: go install golang.org/x/tools/cmd/goimports
|
||||
- run: goimports -local scroll-tech/database/ -w .
|
||||
- run: go mod tidy
|
||||
- name: Run goimports lint
|
||||
working-directory: 'database'
|
||||
run: goimports -local scroll-tech/database/ -w .
|
||||
- name: Run go mod tidy
|
||||
working-directory: 'database'
|
||||
run: go mod tidy
|
||||
# If there are any diffs from goimports or go mod tidy, fail.
|
||||
- name: Verify no changes from goimports and go mod tidy
|
||||
working-directory: 'database'
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
tests:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Solc
|
||||
uses: supplypike/setup-bin@v3
|
||||
with:
|
||||
uri: 'https://github.com/ethereum/solidity/releases/download/v0.8.16/solc-static-linux'
|
||||
name: 'solc'
|
||||
version: '0.8.16'
|
||||
- name: Install Geth Tools
|
||||
uses: gacts/install-geth-tools@v1
|
||||
- name: Build prerequisites
|
||||
run: |
|
||||
make dev_docker
|
||||
- name: Test database packages
|
||||
working-directory: 'database'
|
||||
run: |
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: database
|
||||
|
||||
43
.github/workflows/integration.yaml
vendored
Normal file
43
.github/workflows/integration.yaml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
- develop
|
||||
- alpha
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Solc
|
||||
uses: supplypike/setup-bin@v3
|
||||
with:
|
||||
uri: 'https://github.com/ethereum/solidity/releases/download/v0.8.16/solc-static-linux'
|
||||
name: 'solc'
|
||||
version: '0.8.16'
|
||||
- name: Install Geth Tools
|
||||
uses: gacts/install-geth-tools@v1
|
||||
- name: Build prerequisites
|
||||
run: |
|
||||
make dev_docker
|
||||
make -C bridge mock_abi
|
||||
make -C common/bytecode all
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
go test -v -tags="mock_prover mock_verifier" -p 1 -coverprofile=coverage.txt scroll-tech/integration-test/...
|
||||
59
.github/workflows/intermediate-docker.yml
vendored
Normal file
59
.github/workflows/intermediate-docker.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Intermediate Docker
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
GO_VERSION:
|
||||
description: 'Go version'
|
||||
required: true
|
||||
type: string
|
||||
default: '1.19'
|
||||
RUST_VERSION:
|
||||
description: 'Rust toolchain version'
|
||||
required: true
|
||||
type: string
|
||||
default: 'nightly-2022-12-10'
|
||||
PYTHON_VERSION:
|
||||
description: 'Python version'
|
||||
required: false
|
||||
type: string
|
||||
default: '3.10'
|
||||
CUDA_VERSION:
|
||||
description: 'Cuda version'
|
||||
required: false
|
||||
type: string
|
||||
default: '11.7.1'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: 'build/dockerfiles/intermediate'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build
|
||||
run: |
|
||||
make all
|
||||
env:
|
||||
GO_VERSION: ${{ inputs.GO_VERSION }}
|
||||
RUST_VERSION: ${{ inputs.RUST_VERSION }}
|
||||
PYTHON_VERSION: ${{ inputs.PYTHON_VERSION }}
|
||||
CUDA_VERSION: ${{ inputs.CUDA_VERSION }}
|
||||
- name: Publish
|
||||
run: |
|
||||
make publish
|
||||
env:
|
||||
GO_VERSION: ${{ inputs.GO_VERSION }}
|
||||
RUST_VERSION: ${{ inputs.RUST_VERSION }}
|
||||
PYTHON_VERSION: ${{ inputs.PYTHON_VERSION }}
|
||||
CUDA_VERSION: ${{ inputs.CUDA_VERSION }}
|
||||
14
.github/workflows/roller.yml
vendored
14
.github/workflows/roller.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Cache cargo
|
||||
@@ -47,7 +47,13 @@ jobs:
|
||||
- name: Test
|
||||
run: |
|
||||
make roller
|
||||
go test -tags="mock_prover" -v ./...
|
||||
go test -tags="mock_prover" -v -coverprofile=coverage.txt ./...
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: roller
|
||||
check:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
@@ -55,7 +61,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Lint
|
||||
@@ -69,7 +75,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install goimports
|
||||
|
||||
99
Jenkinsfile
vendored
99
Jenkinsfile
vendored
@@ -1,99 +0,0 @@
|
||||
imagePrefix = 'scrolltech'
|
||||
credentialDocker = 'dockerhub'
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
options {
|
||||
timeout (20)
|
||||
}
|
||||
tools {
|
||||
go 'go-1.18'
|
||||
nodejs "nodejs"
|
||||
}
|
||||
environment {
|
||||
GO111MODULE = 'on'
|
||||
PATH="/home/ubuntu/.cargo/bin:$PATH"
|
||||
LD_LIBRARY_PATH="$LD_LIBRARY_PATH:./coordinator/verifier/lib"
|
||||
CHAIN_ID='534353'
|
||||
// LOG_DOCKER = 'true'
|
||||
}
|
||||
stages {
|
||||
stage('Build') {
|
||||
parallel {
|
||||
stage('Build Prerequisite') {
|
||||
steps {
|
||||
sh 'make dev_docker'
|
||||
sh 'make -C bridge mock_abi'
|
||||
sh 'make -C common/bytecode all'
|
||||
}
|
||||
}
|
||||
stage('Check Bridge Compilation') {
|
||||
steps {
|
||||
sh 'make -C bridge bridge_bins'
|
||||
}
|
||||
}
|
||||
stage('Check Coordinator Compilation') {
|
||||
steps {
|
||||
sh 'export PATH=/home/ubuntu/go/bin:$PATH'
|
||||
sh 'make -C coordinator coordinator'
|
||||
}
|
||||
}
|
||||
stage('Check Database Compilation') {
|
||||
steps {
|
||||
sh 'make -C database db_cli'
|
||||
}
|
||||
}
|
||||
stage('Check Database Docker Build') {
|
||||
steps {
|
||||
sh 'make -C database docker'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Parallel Test') {
|
||||
parallel{
|
||||
stage('Race test common package') {
|
||||
steps {
|
||||
sh 'go test -v -race -coverprofile=coverage.common.txt -covermode=atomic scroll-tech/common/...'
|
||||
}
|
||||
}
|
||||
stage('Race test bridge package') {
|
||||
steps {
|
||||
sh "cd ./bridge && ../build/run_tests.sh bridge"
|
||||
}
|
||||
}
|
||||
stage('Race test coordinator package') {
|
||||
steps {
|
||||
sh 'cd ./coordinator && go test -exec "env LD_LIBRARY_PATH=${PWD}/verifier/lib" -v -race -gcflags="-l" -ldflags="-s=false" -coverpkg="scroll-tech/coordinator" -coverprofile=../coverage.coordinator.txt -covermode=atomic ./...'
|
||||
}
|
||||
}
|
||||
stage('Race test database package') {
|
||||
steps {
|
||||
sh 'go test -v -race -coverprofile=coverage.db.txt -covermode=atomic scroll-tech/database/...'
|
||||
}
|
||||
}
|
||||
stage('Integration test') {
|
||||
steps {
|
||||
sh 'go test -v -tags="mock_prover mock_verifier" -p 1 scroll-tech/integration-test/...'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Compare Coverage') {
|
||||
steps {
|
||||
sh "./build/post-test-report-coverage.sh"
|
||||
script {
|
||||
currentBuild.result = 'SUCCESS'
|
||||
}
|
||||
step([$class: 'CompareCoverageAction', publishResultAs: 'Comment', scmVars: [GIT_URL: env.GIT_URL]])
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
publishCoverage adapters: [coberturaReportAdapter(path: 'cobertura.xml', thresholds: [[thresholdTarget: 'Aggregated Report', unhealthyThreshold: 40.0]])], checksName: '', sourceFileResolver: sourceFiles('NEVER_STORE')
|
||||
cleanWs()
|
||||
slackSend(message: "${JOB_BASE_NAME} ${GIT_COMMIT} #${BUILD_NUMBER} deploy ${currentBuild.result}")
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Makefile
23
Makefile
@@ -1,7 +1,5 @@
|
||||
.PHONY: check update dev_docker build_test_docker run_test_docker clean
|
||||
|
||||
ZKP_VERSION=release-1220
|
||||
|
||||
help: ## Display this help message
|
||||
@grep -h \
|
||||
-E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
@@ -17,12 +15,12 @@ lint: ## The code's format and security checks.
|
||||
|
||||
update: ## update dependencies
|
||||
go work sync
|
||||
cd $(PWD)/bridge/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v3.1.12 && go mod tidy
|
||||
cd $(PWD)/bridge/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v4.1.0 && go mod tidy
|
||||
cd $(PWD)/bridge-history-api/ && go get -u github.com/ethereum/go-ethereum@latest && go mod tidy
|
||||
cd $(PWD)/common/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v3.1.12 && go mod tidy
|
||||
cd $(PWD)/coordinator/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v3.1.12 && go mod tidy
|
||||
cd $(PWD)/database/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v3.1.12 && go mod tidy
|
||||
cd $(PWD)/roller/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v3.1.12 && go mod tidy
|
||||
cd $(PWD)/common/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v4.1.0 && go mod tidy
|
||||
cd $(PWD)/coordinator/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v4.1.0 && go mod tidy
|
||||
cd $(PWD)/database/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v4.1.0 && go mod tidy
|
||||
cd $(PWD)/roller/ && go get -u github.com/scroll-tech/go-ethereum@scroll-v4.1.0 && go mod tidy
|
||||
goimports -local $(PWD)/bridge/ -w .
|
||||
goimports -local $(PWD)/bridge-history-api/ -w .
|
||||
goimports -local $(PWD)/common/ -w .
|
||||
@@ -40,16 +38,5 @@ build_test_docker: ## build Docker image for local testing on M1/M2 Silicon Mac
|
||||
run_test_docker: ## run Docker image for local testing on M1/M2 Silicon Mac
|
||||
docker run -it --rm --name scroll_test_container --network=host -v /var/run/docker.sock:/var/run/docker.sock -v $(PWD):/go/src/app scroll_test_image
|
||||
|
||||
|
||||
test_zkp: ## Test zkp prove and verify, roller/prover generates the proof and coordinator/verifier verifies it
|
||||
mkdir -p test_params
|
||||
wget https://circuit-release.s3.us-west-2.amazonaws.com/circuit-release/${ZKP_VERSION}/test_params/params19 -O ./test_params/params19
|
||||
wget https://circuit-release.s3.us-west-2.amazonaws.com/circuit-release/${ZKP_VERSION}/test_params/params26 -O ./test_params/params26
|
||||
wget https://circuit-release.s3.us-west-2.amazonaws.com/circuit-release/${ZKP_VERSION}/test_seed -O test_seed
|
||||
rm -rf ./roller/assets/test_params && mv test_params ./roller/assets/ && mv test_seed ./roller/assets/
|
||||
cd ./roller && make test-gpu-prover
|
||||
rm -rf ./coordinator/assets/test_params && mv ./roller/assets/test_params ./coordinator/assets/ && mv ./roller/assets/agg_proof ./coordinator/assets/
|
||||
cd ./coordinator && make test-gpu-verifier
|
||||
|
||||
clean: ## Empty out the bin folder
|
||||
@rm -rf build/bin
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Scroll Monorepo
|
||||
|
||||
[](https://github.com/scroll-tech/scroll/actions/workflows/contracts.yaml) [](https://github.com/scroll-tech/scroll/actions/workflows/bridge.yml) [](https://github.com/scroll-tech/scroll/actions/workflows/coordinator.yml) [](https://github.com/scroll-tech/scroll/actions/workflows/database.yml) [](https://github.com/scroll-tech/scroll/actions/workflows/common.yml) [](https://github.com/scroll-tech/scroll/actions/workflows/roller.yml)
|
||||
[](https://codecov.io/gh/scroll-tech/scroll)
|
||||
|
||||
## Prerequisites
|
||||
+ Go 1.18
|
||||
+ Go 1.19
|
||||
+ Rust (for version, see [rust-toolchain](./common/libzkp/impl/rust-toolchain))
|
||||
+ Hardhat / Foundry
|
||||
+ Docker
|
||||
|
||||
@@ -43,6 +43,8 @@ var (
|
||||
|
||||
// ScrollChainABI holds information about ScrollChain's context and available invokable methods.
|
||||
ScrollChainABI *abi.ABI
|
||||
// ScrollChainV2ABI holds information about ScrollChainV2's context and available invokable methods.
|
||||
ScrollChainV2ABI *abi.ABI
|
||||
// L1ScrollMessengerABI holds information about L1ScrollMessenger's context and available invokable methods.
|
||||
L1ScrollMessengerABI *abi.ABI
|
||||
// L1MessageQueueABI holds information about L1MessageQueue contract's context and available invokable methods.
|
||||
@@ -116,6 +118,7 @@ func init() {
|
||||
|
||||
// scroll monorepo
|
||||
ScrollChainABI, _ = ScrollChainMetaData.GetAbi()
|
||||
ScrollChainV2ABI, _ = ScrollChainV2MetaData.GetAbi()
|
||||
L1ScrollMessengerABI, _ = L1ScrollMessengerMetaData.GetAbi()
|
||||
L1MessageQueueABI, _ = L1MessageQueueMetaData.GetAbi()
|
||||
L2GasPriceOracleABI, _ = L2GasPriceOracleMetaData.GetAbi()
|
||||
@@ -198,6 +201,11 @@ var ScrollChainMetaData = &bind.MetaData{
|
||||
ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"}],\"name\":\"CommitBatch\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"}],\"name\":\"FinalizeBatch\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"}],\"name\":\"RevertBatch\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"parentHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"blockNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"timestamp\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"baseFee\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"numTransactions\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"numL1Messages\",\"type\":\"uint16\"}],\"internalType\":\"structIScrollChain.BlockContext[]\",\"name\":\"blocks\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes32\",\"name\":\"prevStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"newStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"withdrawTrieRoot\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"batchIndex\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"parentBatchHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"l2Transactions\",\"type\":\"bytes\"}],\"internalType\":\"structIScrollChain.Batch\",\"name\":\"batch\",\"type\":\"tuple\"}],\"name\":\"commitBatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"parentHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"blockNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"timestamp\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"baseFee\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"numTransactions\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"numL1Messages\",\"type\":\"uint16\"}],\"internalType\":\"structIScrollChain.BlockContext[]\",\"name\":\"blocks\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes32\",\"name\":\"prevStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"newStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"withdrawTrieRoot\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"batchIndex\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"parentBatchHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"l2Transactions\",\"type\":\"bytes\"}],\"internalType\":\"structIScrollChain.Batch[]\",\"name\":\"batches\",\"type\":\"tuple[]\"}],\"name\":\"commitBatches\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256[]\",\"name\":\"proof\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"instances\",\"type\":\"uint256[]\"}],\"name\":\"finalizeBatchWithProof\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"}],\"name\":\"getL2MessageRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"}],\"name\":\"isBatchFinalized\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"}],\"name\":\"revertBatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
|
||||
}
|
||||
|
||||
// ScrollChainV2MetaData contains all meta data concerning the ScrollChain contract.
|
||||
var ScrollChainV2MetaData = &bind.MetaData{
|
||||
ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"}],\"name\":\"CommitBatch\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"withdrawRoot\",\"type\":\"bytes32\"}],\"name\":\"FinalizeBatch\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"}],\"name\":\"RevertBatch\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"parentBatchHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes[]\",\"name\":\"chunks\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"skippedL1MessageBitmap\",\"type\":\"bytes\"}],\"name\":\"commitBatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"batchIndex\",\"type\":\"uint256\"}],\"name\":\"committedBatches\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"batchHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"prevStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"postStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"withdrawRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"aggrProof\",\"type\":\"bytes\"}],\"name\":\"finalizeBatchWithProof\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"batchIndex\",\"type\":\"uint256\"}],\"name\":\"finalizedStateRoots\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"batchIndex\",\"type\":\"uint256\"}],\"name\":\"isBatchFinalized\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"batchHeader\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"count\",\"type\":\"uint256\"}],\"name\":\"revertBatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"batchIndex\",\"type\":\"uint256\"}],\"name\":\"withdrawRoots\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
|
||||
}
|
||||
|
||||
// L1ScrollMessengerMetaData contains all meta data concerning the L1ScrollMessenger contract.
|
||||
var L1ScrollMessengerMetaData = &bind.MetaData{
|
||||
ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"}],\"name\":\"FailedRelayedMessage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"}],\"name\":\"RelayedMessage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"messageNonce\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"SentMessage\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"batchHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"merkleProof\",\"type\":\"bytes\"}],\"internalType\":\"structIL1ScrollMessenger.L2MessageProof\",\"name\":\"proof\",\"type\":\"tuple\"}],\"name\":\"relayMessageWithProof\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"queueIndex\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"uint32\",\"name\":\"oldGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"newGasLimit\",\"type\":\"uint32\"}],\"name\":\"replayMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"}],\"name\":\"sendMessage\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"xDomainMessageSender\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
|
||||
@@ -325,3 +333,31 @@ type L2FailedRelayedMessageEvent struct {
|
||||
type L2RelayedMessageEvent struct {
|
||||
MessageHash common.Hash
|
||||
}
|
||||
|
||||
// IScrollChainBatch is an auto generated low-level Go binding around an user-defined struct.
|
||||
type IScrollChainBatch struct {
|
||||
Blocks []IScrollChainBlockContext
|
||||
PrevStateRoot common.Hash
|
||||
NewStateRoot common.Hash
|
||||
WithdrawTrieRoot common.Hash
|
||||
BatchIndex uint64
|
||||
ParentBatchHash common.Hash
|
||||
L2Transactions []byte
|
||||
}
|
||||
|
||||
// L1CommitBatchEvent represents a CommitBatch event raised by the ScrollChain contract.
|
||||
type L1CommitBatchEvent struct {
|
||||
BatchHash common.Hash
|
||||
}
|
||||
|
||||
// IScrollChainBlockContext is an auto generated low-level Go binding around an user-defined struct.
|
||||
type IScrollChainBlockContext struct {
|
||||
BlockHash common.Hash
|
||||
ParentHash common.Hash
|
||||
BlockNumber uint64
|
||||
Timestamp uint64
|
||||
BaseFee *big.Int
|
||||
GasLimit uint64
|
||||
NumTransactions uint16
|
||||
NumL1Messages uint16
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/iris-contrib/middleware/cors"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/mvc"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -60,6 +61,11 @@ func init() {
|
||||
}
|
||||
|
||||
func action(ctx *cli.Context) error {
|
||||
corsOptions := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||
AllowCredentials: true,
|
||||
})
|
||||
// Load config file.
|
||||
cfgFile := ctx.String(cutils.ConfigFileFlag.Name)
|
||||
cfg, err := config.NewConfig(cfgFile)
|
||||
@@ -72,13 +78,14 @@ func action(ctx *cli.Context) error {
|
||||
}
|
||||
defer database.Close()
|
||||
bridgeApp := iris.New()
|
||||
bridgeApp.UseRouter(corsOptions)
|
||||
bridgeApp.Get("/ping", pong).Describe("healthcheck")
|
||||
|
||||
mvc.Configure(bridgeApp.Party("/api/txs"), setupQueryByAddressHandler)
|
||||
mvc.Configure(bridgeApp.Party("/api/txsbyhashes"), setupQueryByHashHandler)
|
||||
|
||||
// TODO: make debug mode configurable
|
||||
err = bridgeApp.Listen(":8080", iris.WithLogLevel("debug"))
|
||||
err = bridgeApp.Listen(cfg.Server.HostPort, iris.WithLogLevel("debug"))
|
||||
if err != nil {
|
||||
log.Crit("can not start server", "err", err)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"bridge-history-api/config"
|
||||
"bridge-history-api/cross_msg"
|
||||
"bridge-history-api/cross_msg/message_proof"
|
||||
"bridge-history-api/db"
|
||||
cutils "bridge-history-api/utils"
|
||||
)
|
||||
@@ -54,6 +55,7 @@ func action(ctx *cli.Context) error {
|
||||
log.Crit("failed to connect l2 geth", "config file", cfgFile, "error", err)
|
||||
}
|
||||
db, err := db.NewOrmFactory(cfg)
|
||||
defer db.Close()
|
||||
if err != nil {
|
||||
log.Crit("failed to connect to db", "config file", cfgFile, "error", err)
|
||||
}
|
||||
@@ -98,6 +100,21 @@ func action(ctx *cli.Context) error {
|
||||
go l2crossMsgFetcher.Start()
|
||||
defer l2crossMsgFetcher.Stop()
|
||||
|
||||
// BlockTimestamp fetcher for l1 and l2
|
||||
l1BlockTimeFetcher := cross_msg.NewBlockTimestampFetcher(subCtx, cfg.L1.Confirmation, int(cfg.L1.BlockTime), l1client, db.UpdateL1BlockTimestamp, db.GetL1EarliestNoBlockTimestampHeight)
|
||||
go l1BlockTimeFetcher.Start()
|
||||
defer l1BlockTimeFetcher.Stop()
|
||||
|
||||
l2BlockTimeFetcher := cross_msg.NewBlockTimestampFetcher(subCtx, cfg.L2.Confirmation, int(cfg.L2.BlockTime), l2client, db.UpdateL2BlockTimestamp, db.GetL2EarliestNoBlockTimestampHeight)
|
||||
go l2BlockTimeFetcher.Start()
|
||||
defer l2BlockTimeFetcher.Stop()
|
||||
|
||||
// Proof updater and batch fetcher
|
||||
l2msgProofUpdater := message_proof.NewMsgProofUpdater(subCtx, cfg.L1.Confirmation, cfg.BatchInfoFetcher.BatchIndexStartBlock, db)
|
||||
batchFetcher := cross_msg.NewBatchInfoFetcher(subCtx, common.HexToAddress(cfg.BatchInfoFetcher.ScrollChainAddr), cfg.BatchInfoFetcher.BatchIndexStartBlock, cfg.L1.Confirmation, int(cfg.L1.BlockTime), l1client, db, l2msgProofUpdater)
|
||||
go batchFetcher.Start()
|
||||
defer batchFetcher.Stop()
|
||||
|
||||
// Catch CTRL-C to ensure a graceful shutdown.
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
@@ -1,34 +1,41 @@
|
||||
{
|
||||
"batchInfoFetcher": {
|
||||
"batchIndexStartBlock": 9091265,
|
||||
"ScrollChainAddr": "0xcD00DB804C819175B381b2B44Aa16A391c8a01D6"
|
||||
},
|
||||
"l1": {
|
||||
"confirmation": 64,
|
||||
"endpoint": "https://rpc.ankr.com/eth_goerli",
|
||||
"startHeight": 8890194 ,
|
||||
"startHeight": 9090194 ,
|
||||
"blockTime": 10,
|
||||
"MessengerAddr": "0x5260e38080BFe97e6C4925d9209eCc5f964373b6",
|
||||
"ETHGatewayAddr": "0x429b73A21cF3BF1f3E696a21A95408161daF311f",
|
||||
"WETHGatewayAddr": "0x8be69E499D8848DfFb4cF9bac909f3e2cF2FeFa0",
|
||||
"StandardERC20Gateway": "0xeF37207c1A1efF6D6a9d7BfF3cF4270e406d319b",
|
||||
"CustomERC20GatewayAddr": "0x920f906B814597cF5DC76F95100F09CBAF9c5748",
|
||||
"ERC721GatewayAddr": "0x1C441Dfc5C2eD7A2AA8636748A664E59CB029157",
|
||||
"ERC1155GatewayAddr": "0xd1bE599aaCBC21448fD6373bbc7c1b4c7806f135"
|
||||
"MessengerAddr": "0x326517Eb8eB1Ce5eaB5b513C2e9A24839b402d90",
|
||||
"ETHGatewayAddr": "0x8305cB7B8448677736095965B63d7431017328fe",
|
||||
"WETHGatewayAddr": "0xe3bA3c60d99a2d9a5f817734bC85353470b23931",
|
||||
"StandardERC20Gateway": "0x16c1079B27eD9c363B7D08aC5Ae937A398972A5C",
|
||||
"CustomERC20GatewayAddr": "0x61f08caD3d6F77801167d3bA8669433701586643",
|
||||
"ERC721GatewayAddr": "0x4A73D25A4C99CB912acaf6C5B5e554f2982201c5",
|
||||
"ERC1155GatewayAddr": "0xa3F5DD3033698c2832C53f3C3Fe6E062F58cD808"
|
||||
},
|
||||
"l2": {
|
||||
"confirmation": 1,
|
||||
"endpoint": "https://alpha-rpc.scroll.io/l2",
|
||||
"endpoint": "http://staging-l2geth-rpc0.scroll.tech:8545",
|
||||
"blockTime": 3,
|
||||
"startHeight": 1600068,
|
||||
"CustomERC20GatewayAddr": "0xa07Cb742657294C339fB4d5d6CdF3fdBeE8C1c68",
|
||||
"ERC721GatewayAddr": "0x8Fee20e0C0Ef16f2898a8073531a857D11b9C700",
|
||||
"StandardERC20Gateway": "0xB878F37BB278bf0e4974856fFe86f5e6F66BD725",
|
||||
"MessengerAddr": "0xb75d7e84517e1504C151B270255B087Fd746D34C",
|
||||
"ETHGatewayAddr": "0x32139B5C8838E94fFcD83E60dff95Daa7F0bA14c",
|
||||
"WETHGatewayAddr": "0xBb88bF582F2BBa46702621dae5CB9271057bC85b",
|
||||
"ERC1155GatewayAddr": "0x2946cB860028276b3C4bccE1767841641C2E0828"
|
||||
"startHeight": 0,
|
||||
"CustomERC20GatewayAddr": "0x905db21f836749fEeD12de781afc4A5Ab4Dd0d51",
|
||||
"ERC721GatewayAddr": "0xC53D835514780664BCd7eCfcE7c2E5d9554dc41B",
|
||||
"StandardERC20Gateway": "0x90271634BCB020e06ea4840C3f7aa61b8F860651",
|
||||
"MessengerAddr": "0xE8b0956Ac75c65Aa1669e83888DA13afF2E108f4",
|
||||
"ETHGatewayAddr": "0xD5938590D5dD8ce95812D4D515a219C12C551D67",
|
||||
"WETHGatewayAddr": "0xb0aaA582564fade4232a16fdB1383004A6A7247F",
|
||||
"ERC1155GatewayAddr": "0x4f33B1655619c2C0B7C450128Df760B4365Cb549"
|
||||
},
|
||||
"db": {
|
||||
"dsn": "postgres://postgres:1234@localhost:5444/test?sslmode=disable",
|
||||
"driverName": "postgres",
|
||||
"maxOpenNum": 200,
|
||||
"maxIdleNum": 20
|
||||
},
|
||||
"server": {
|
||||
"hostPort": "0.0.0.0:20006"
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type BatchInfoFetcherConfig struct {
|
||||
BatchIndexStartBlock uint64 `json:"batchIndexStartBlock"`
|
||||
ScrollChainAddr string `json:"ScrollChainAddr"`
|
||||
}
|
||||
|
||||
// DBConfig db config
|
||||
type DBConfig struct {
|
||||
// data source name
|
||||
@@ -30,6 +35,10 @@ type LayerConfig struct {
|
||||
CustomERC20GatewayAddr string `json:"CustomERC20GatewayAddr"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
HostPort string `json:"hostPort"`
|
||||
}
|
||||
|
||||
// Config is the configuration of the bridge history backend
|
||||
type Config struct {
|
||||
// chain config
|
||||
@@ -37,7 +46,9 @@ type Config struct {
|
||||
L2 *LayerConfig `json:"l2"`
|
||||
|
||||
// data source name
|
||||
DB *DBConfig `json:"db"`
|
||||
DB *DBConfig `json:"db"`
|
||||
Server *ServerConfig `json:"server"`
|
||||
BatchInfoFetcher *BatchInfoFetcherConfig `json:"batchInfoFetcher"`
|
||||
}
|
||||
|
||||
// NewConfig returns a new instance of Config.
|
||||
|
||||
@@ -16,7 +16,7 @@ type QueryHashController struct {
|
||||
}
|
||||
|
||||
func (c *QueryAddressController) Get(req model.QueryByAddressRequest) (*model.QueryByAddressResponse, error) {
|
||||
message, err := c.Service.GetTxsByAddress(common.HexToAddress(req.Address), int64(req.Offset), int64(req.Limit))
|
||||
message, total, err := c.Service.GetTxsByAddress(common.HexToAddress(req.Address), int64(req.Offset), int64(req.Limit))
|
||||
if err != nil {
|
||||
return &model.QueryByAddressResponse{Message: "500", Data: &model.Data{}}, err
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func (c *QueryAddressController) Get(req model.QueryByAddressRequest) (*model.Qu
|
||||
return &model.QueryByAddressResponse{Message: "ok",
|
||||
Data: &model.Data{
|
||||
Result: message,
|
||||
Total: len(message),
|
||||
Total: total,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
|
||||
105
bridge-history-api/cross_msg/batch_info_fetcher.go
Normal file
105
bridge-history-api/cross_msg/batch_info_fetcher.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package cross_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"bridge-history-api/cross_msg/message_proof"
|
||||
"bridge-history-api/db"
|
||||
"bridge-history-api/utils"
|
||||
)
|
||||
|
||||
type BatchInfoFetcher struct {
|
||||
ctx context.Context
|
||||
scrollChainAddr common.Address
|
||||
batchInfoStartNumber uint64
|
||||
confirmation uint64
|
||||
blockTimeInSec int
|
||||
client *ethclient.Client
|
||||
db db.OrmFactory
|
||||
msgProofUpdater *message_proof.MsgProofUpdater
|
||||
}
|
||||
|
||||
func NewBatchInfoFetcher(ctx context.Context, scrollChainAddr common.Address, batchInfoStartNumber uint64, confirmation uint64, blockTimeInSec int, client *ethclient.Client, db db.OrmFactory, msgProofUpdater *message_proof.MsgProofUpdater) *BatchInfoFetcher {
|
||||
return &BatchInfoFetcher{
|
||||
ctx: ctx,
|
||||
scrollChainAddr: scrollChainAddr,
|
||||
batchInfoStartNumber: batchInfoStartNumber,
|
||||
confirmation: confirmation,
|
||||
blockTimeInSec: blockTimeInSec,
|
||||
client: client,
|
||||
db: db,
|
||||
msgProofUpdater: msgProofUpdater,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BatchInfoFetcher) Start() {
|
||||
log.Info("BatchInfoFetcher Start")
|
||||
// Fetch batch info at beginning
|
||||
// Then start msg proof updater after db have some bridge batch
|
||||
err := b.fetchBatchInfo()
|
||||
if err != nil {
|
||||
log.Error("fetch batch info at begining failed: ", "err", err)
|
||||
}
|
||||
|
||||
go b.msgProofUpdater.Start()
|
||||
|
||||
go func() {
|
||||
tick := time.NewTicker(time.Duration(b.blockTimeInSec) * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
tick.Stop()
|
||||
return
|
||||
case <-tick.C:
|
||||
err := b.fetchBatchInfo()
|
||||
if err != nil {
|
||||
log.Error("fetch batch info failed: ", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *BatchInfoFetcher) Stop() {
|
||||
log.Info("BatchInfoFetcher Stop")
|
||||
b.msgProofUpdater.Stop()
|
||||
}
|
||||
|
||||
func (b *BatchInfoFetcher) fetchBatchInfo() error {
|
||||
number, err := utils.GetSafeBlockNumber(b.ctx, b.client, b.confirmation)
|
||||
if err != nil {
|
||||
log.Error("Can not get latest block number: ", "err", err)
|
||||
return err
|
||||
}
|
||||
latestBatch, err := b.db.GetLatestRollupBatch()
|
||||
if err != nil {
|
||||
log.Error("Can not get latest BatchInfo: ", "err", err)
|
||||
return err
|
||||
}
|
||||
var startHeight uint64
|
||||
if latestBatch == nil {
|
||||
startHeight = b.batchInfoStartNumber
|
||||
} else {
|
||||
startHeight = latestBatch.CommitHeight + 1
|
||||
}
|
||||
for from := startHeight; number >= from; from += uint64(fetchLimit) {
|
||||
to := from + uint64(fetchLimit) - 1
|
||||
// number - confirmation can never less than 0 since the for loop condition
|
||||
// but watch out the overflow
|
||||
if to > number {
|
||||
to = number
|
||||
}
|
||||
// filter logs to fetch batches
|
||||
err = FetchAndSaveBatchIndex(b.ctx, b.client, b.db, int64(from), int64(to), b.scrollChainAddr)
|
||||
if err != nil {
|
||||
log.Error("Can not fetch and save from chain: ", "err", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
78
bridge-history-api/cross_msg/block_timestamp_fetcher.go
Normal file
78
bridge-history-api/cross_msg/block_timestamp_fetcher.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package cross_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type GetEarliestNoBlockTimestampHeightFunc func() (uint64, error)
|
||||
type UpdateBlockTimestampFunc func(height uint64, timestamp time.Time) error
|
||||
|
||||
type BlockTimestampFetcher struct {
|
||||
ctx context.Context
|
||||
confirmation uint64
|
||||
blockTimeInSec int
|
||||
client *ethclient.Client
|
||||
updateBlockTimestampFunc UpdateBlockTimestampFunc
|
||||
getEarliestNoBlockTimestampHeightFunc GetEarliestNoBlockTimestampHeightFunc
|
||||
}
|
||||
|
||||
func NewBlockTimestampFetcher(ctx context.Context, confirmation uint64, blockTimeInSec int, client *ethclient.Client, updateBlockTimestampFunc UpdateBlockTimestampFunc, getEarliestNoBlockTimestampHeightFunc GetEarliestNoBlockTimestampHeightFunc) *BlockTimestampFetcher {
|
||||
return &BlockTimestampFetcher{
|
||||
ctx: ctx,
|
||||
confirmation: confirmation,
|
||||
blockTimeInSec: blockTimeInSec,
|
||||
client: client,
|
||||
getEarliestNoBlockTimestampHeightFunc: getEarliestNoBlockTimestampHeightFunc,
|
||||
updateBlockTimestampFunc: updateBlockTimestampFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BlockTimestampFetcher) Start() {
|
||||
go func() {
|
||||
tick := time.NewTicker(time.Duration(b.blockTimeInSec) * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
tick.Stop()
|
||||
return
|
||||
case <-tick.C:
|
||||
number, err := b.client.BlockNumber(b.ctx)
|
||||
if err != nil {
|
||||
log.Error("Can not get latest block number", "err", err)
|
||||
continue
|
||||
}
|
||||
startHeight, err := b.getEarliestNoBlockTimestampHeightFunc()
|
||||
if err != nil {
|
||||
log.Error("Can not get latest record without block timestamp", "err", err)
|
||||
continue
|
||||
}
|
||||
for height := startHeight; number >= height+b.confirmation && height > 0; {
|
||||
block, err := b.client.HeaderByNumber(b.ctx, new(big.Int).SetUint64(height))
|
||||
if err != nil {
|
||||
log.Error("Can not get block by number", "err", err)
|
||||
break
|
||||
}
|
||||
err = b.updateBlockTimestampFunc(height, time.Unix(int64(block.Time), 0))
|
||||
if err != nil {
|
||||
log.Error("Can not update blockTimestamp into DB ", "err", err)
|
||||
break
|
||||
}
|
||||
height, err = b.getEarliestNoBlockTimestampHeightFunc()
|
||||
if err != nil {
|
||||
log.Error("Can not get latest record without block timestamp", "err", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *BlockTimestampFetcher) Stop() {
|
||||
log.Info("BlockTimestampFetcher Stop")
|
||||
}
|
||||
@@ -27,13 +27,12 @@ type CrossMsgFetcher struct {
|
||||
reorgHandling ReorgHandling
|
||||
addressList []common.Address
|
||||
cachedHeaders []*types.Header
|
||||
mu *sync.Mutex
|
||||
mu sync.Mutex
|
||||
reorgStartCh chan struct{}
|
||||
reorgEndCh chan struct{}
|
||||
}
|
||||
|
||||
func NewCrossMsgFetcher(ctx context.Context, config *config.LayerConfig, db db.OrmFactory, client *ethclient.Client, worker *FetchEventWorker, addressList []common.Address, reorg ReorgHandling) (*CrossMsgFetcher, error) {
|
||||
newMU := &sync.Mutex{}
|
||||
crossMsgFetcher := &CrossMsgFetcher{
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
@@ -41,7 +40,6 @@ func NewCrossMsgFetcher(ctx context.Context, config *config.LayerConfig, db db.O
|
||||
client: client,
|
||||
worker: worker,
|
||||
reorgHandling: reorg,
|
||||
mu: newMU,
|
||||
addressList: addressList,
|
||||
cachedHeaders: make([]*types.Header, 0),
|
||||
reorgStartCh: make(chan struct{}),
|
||||
@@ -97,7 +95,6 @@ func (c *CrossMsgFetcher) Start() {
|
||||
}
|
||||
|
||||
func (c *CrossMsgFetcher) Stop() {
|
||||
c.db.Close()
|
||||
log.Info("CrossMsgFetcher Stop")
|
||||
}
|
||||
|
||||
@@ -106,7 +103,7 @@ func (c *CrossMsgFetcher) forwardFetchAndSaveMissingEvents(confirmation uint64)
|
||||
// if we fetch to the latest block, shall not exceed cachedHeaders
|
||||
var number uint64
|
||||
var err error
|
||||
if len(c.cachedHeaders) != 0 && confirmation <= 0 {
|
||||
if len(c.cachedHeaders) != 0 && confirmation == 0 {
|
||||
number = c.cachedHeaders[len(c.cachedHeaders)-1].Number.Uint64() - 1
|
||||
} else {
|
||||
number, err = utils.GetSafeBlockNumber(c.ctx, c.client, confirmation)
|
||||
@@ -119,22 +116,22 @@ func (c *CrossMsgFetcher) forwardFetchAndSaveMissingEvents(confirmation uint64)
|
||||
log.Error(fmt.Sprintf("%s: invalid get/fetch function", c.worker.Name))
|
||||
return
|
||||
}
|
||||
processed_height, err := c.worker.G(c.db)
|
||||
processedHeight, err := c.worker.G(c.db)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("%s: can not get latest processed block height", c.worker.Name))
|
||||
}
|
||||
log.Info(fmt.Sprintf("%s: ", c.worker.Name), "height", processed_height)
|
||||
if processed_height <= 0 || processed_height < int64(c.config.StartHeight) {
|
||||
processed_height = int64(c.config.StartHeight)
|
||||
log.Info(fmt.Sprintf("%s: ", c.worker.Name), "height", processedHeight)
|
||||
if processedHeight <= 0 || processedHeight < int64(c.config.StartHeight) {
|
||||
processedHeight = int64(c.config.StartHeight)
|
||||
} else {
|
||||
processed_height += 1
|
||||
processedHeight += 1
|
||||
}
|
||||
for n := processed_height; n <= int64(number); n += FETCH_LIMIT {
|
||||
iter_end := n + FETCH_LIMIT - 1
|
||||
if iter_end > int64(number) {
|
||||
iter_end = int64(number)
|
||||
for from := processedHeight; from <= int64(number); from += fetchLimit {
|
||||
to := from + fetchLimit - 1
|
||||
if to > int64(number) {
|
||||
to = int64(number)
|
||||
}
|
||||
err := c.worker.F(c.ctx, c.client, c.db, n, iter_end, c.addressList)
|
||||
err := c.worker.F(c.ctx, c.client, c.db, from, to, c.addressList)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("%s: failed!", c.worker.Name), "err", err)
|
||||
break
|
||||
|
||||
@@ -6,21 +6,19 @@ import (
|
||||
|
||||
geth "github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
backendabi "bridge-history-api/abi"
|
||||
"bridge-history-api/db"
|
||||
"bridge-history-api/db/orm"
|
||||
"bridge-history-api/utils"
|
||||
)
|
||||
|
||||
// Todo : read from config
|
||||
var (
|
||||
// the number of blocks fetch per round
|
||||
FETCH_LIMIT = int64(3000)
|
||||
fetchLimit = int64(3000)
|
||||
)
|
||||
|
||||
// FetchAndSave is a function type that fetches events from blockchain and saves them to database
|
||||
@@ -36,11 +34,6 @@ type FetchEventWorker struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type msgHashWrapper struct {
|
||||
msgHash common.Hash
|
||||
txHash common.Hash
|
||||
}
|
||||
|
||||
func GetLatestL1ProcessedHeight(db db.OrmFactory) (int64, error) {
|
||||
crossHeight, err := db.GetLatestL1ProcessedHeight()
|
||||
if err != nil {
|
||||
@@ -70,15 +63,22 @@ func GetLatestL2ProcessedHeight(db db.OrmFactory) (int64, error) {
|
||||
log.Error("failed to get L2 relayed message processed height", "err", err)
|
||||
return 0, err
|
||||
}
|
||||
if crossHeight > relayedHeight {
|
||||
return crossHeight, nil
|
||||
} else {
|
||||
return relayedHeight, nil
|
||||
l2SentHeight, err := db.GetLatestSentMsgHeightOnL2()
|
||||
if err != nil {
|
||||
log.Error("failed to get L2 sent message processed height", "err", err)
|
||||
return 0, err
|
||||
}
|
||||
maxHeight := crossHeight
|
||||
if maxHeight < relayedHeight {
|
||||
maxHeight = relayedHeight
|
||||
}
|
||||
if maxHeight < l2SentHeight {
|
||||
maxHeight = l2SentHeight
|
||||
}
|
||||
return maxHeight, nil
|
||||
}
|
||||
|
||||
func L1FetchAndSaveEvents(ctx context.Context, client *ethclient.Client, database db.OrmFactory, from int64, to int64, addrList []common.Address) error {
|
||||
|
||||
query := geth.FilterQuery{
|
||||
FromBlock: big.NewInt(from), // inclusive
|
||||
ToBlock: big.NewInt(to), // inclusive
|
||||
@@ -99,7 +99,7 @@ func L1FetchAndSaveEvents(ctx context.Context, client *ethclient.Client, databas
|
||||
log.Warn("Failed to get l1 event logs", "err", err)
|
||||
return err
|
||||
}
|
||||
depositL1CrossMsgs, msgHashes, relayedMsg, err := parseBackendL1EventLogs(logs)
|
||||
depositL1CrossMsgs, msgHashes, relayedMsg, err := utils.ParseBackendL1EventLogs(logs)
|
||||
if err != nil {
|
||||
log.Error("l1FetchAndSaveEvents: Failed to parse cross msg event logs", "err", err)
|
||||
return err
|
||||
@@ -157,7 +157,7 @@ func L2FetchAndSaveEvents(ctx context.Context, client *ethclient.Client, databas
|
||||
log.Warn("Failed to get l2 event logs", "err", err)
|
||||
return err
|
||||
}
|
||||
depositL2CrossMsgs, msgHashes, relayedMsg, err := parseBackendL2EventLogs(logs)
|
||||
depositL2CrossMsgs, msgHashes, relayedMsg, l2sentMsgs, err := utils.ParseBackendL2EventLogs(logs)
|
||||
if err != nil {
|
||||
log.Error("l2FetchAndSaveEvents: Failed to parse cross msg event logs", "err", err)
|
||||
return err
|
||||
@@ -178,11 +178,19 @@ func L2FetchAndSaveEvents(ctx context.Context, client *ethclient.Client, databas
|
||||
dbTx.Rollback()
|
||||
log.Crit("l2FetchAndSaveEvents: Failed to insert relayed message event logs", "err", err)
|
||||
}
|
||||
|
||||
err = updateL2CrossMsgMsgHash(ctx, dbTx, database, msgHashes)
|
||||
if err != nil {
|
||||
dbTx.Rollback()
|
||||
log.Crit("l2FetchAndSaveEvents: Failed to update msgHash in L2 cross msg", "err", err)
|
||||
}
|
||||
|
||||
err = database.BatchInsertL2SentMsgDBTx(dbTx, l2sentMsgs)
|
||||
if err != nil {
|
||||
dbTx.Rollback()
|
||||
log.Crit("l2FetchAndSaveEvents: Failed to insert l2 sent message", "err", err)
|
||||
}
|
||||
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
// if we can not insert into DB, there must something wrong, need a on-call member handle the dababase manually
|
||||
@@ -194,234 +202,61 @@ func L2FetchAndSaveEvents(ctx context.Context, client *ethclient.Client, databas
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBackendL1EventLogs(logs []types.Log) ([]*orm.CrossMsg, []msgHashWrapper, []*orm.RelayedMsg, error) {
|
||||
// Need use contract abi to parse event Log
|
||||
// Can only be tested after we have our contracts set up
|
||||
|
||||
var l1CrossMsg []*orm.CrossMsg
|
||||
var relayedMsgs []*orm.RelayedMsg
|
||||
var msgHashes []msgHashWrapper
|
||||
for _, vlog := range logs {
|
||||
switch vlog.Topics[0] {
|
||||
case backendabi.L1DepositETHSig:
|
||||
event := backendabi.DepositETH{}
|
||||
err := utils.UnpackLog(backendabi.L1ETHGatewayABI, &event, "DepositETH", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositETH event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ETH),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
case backendabi.L1DepositERC20Sig:
|
||||
event := backendabi.ERC20MessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L1StandardERC20GatewayABI, &event, "DepositERC20", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositERC20 event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ERC20),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
})
|
||||
case backendabi.L1DepositERC721Sig:
|
||||
event := backendabi.ERC721MessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L1ERC721GatewayABI, &event, "DepositERC721", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositERC721 event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC721),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
})
|
||||
case backendabi.L1DepositERC1155Sig:
|
||||
event := backendabi.ERC1155MessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L1ERC1155GatewayABI, &event, "DepositERC1155", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositERC1155 event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC1155),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
Amount: event.Amount.String(),
|
||||
})
|
||||
case backendabi.L1SentMessageEventSignature:
|
||||
event := backendabi.L1SentMessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L1ScrollMessengerABI, &event, "SentMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack SentMessage event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
msgHash := utils.ComputeMessageHash(event.Sender, event.Target, event.Value, event.MessageNonce, event.Message)
|
||||
msgHashes = append(msgHashes, msgHashWrapper{
|
||||
msgHash: msgHash,
|
||||
txHash: vlog.TxHash})
|
||||
case backendabi.L1RelayedMessageEventSignature:
|
||||
event := backendabi.L1RelayedMessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L1ScrollMessengerABI, &event, "RelayedMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack RelayedMessage event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
relayedMsgs = append(relayedMsgs, &orm.RelayedMsg{
|
||||
MsgHash: event.MessageHash.String(),
|
||||
Height: vlog.BlockNumber,
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func FetchAndSaveBatchIndex(ctx context.Context, client *ethclient.Client, database db.OrmFactory, from int64, to int64, scrollChainAddr common.Address) error {
|
||||
query := geth.FilterQuery{
|
||||
FromBlock: big.NewInt(from), // inclusive
|
||||
ToBlock: big.NewInt(to), // inclusive
|
||||
Addresses: []common.Address{scrollChainAddr},
|
||||
Topics: make([][]common.Hash, 1),
|
||||
}
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, nil
|
||||
query.Topics[0] = make([]common.Hash, 1)
|
||||
query.Topics[0][0] = backendabi.L1CommitBatchEventSignature
|
||||
logs, err := client.FilterLogs(ctx, query)
|
||||
if err != nil {
|
||||
log.Warn("Failed to get batch commit event logs", "err", err)
|
||||
return err
|
||||
}
|
||||
rollupBatches, err := utils.ParseBatchInfoFromScrollChain(ctx, client, logs)
|
||||
if err != nil {
|
||||
log.Error("FetchAndSaveBatchIndex: Failed to parse batch commit msg event logs", "err", err)
|
||||
return err
|
||||
}
|
||||
dbTx, err := database.Beginx()
|
||||
if err != nil {
|
||||
log.Error("FetchAndSaveBatchIndex: Failed to begin db transaction", "err", err)
|
||||
return err
|
||||
}
|
||||
err = database.BatchInsertRollupBatchDBTx(dbTx, rollupBatches)
|
||||
if err != nil {
|
||||
dbTx.Rollback()
|
||||
log.Crit("FetchAndSaveBatchIndex: Failed to insert batch commit msg event logs", "err", err)
|
||||
}
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
// if we can not insert into DB, there must something wrong, need a on-call member handle the dababase manually
|
||||
dbTx.Rollback()
|
||||
log.Error("FetchAndSaveBatchIndex: Failed to commit db transaction", "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBackendL2EventLogs(logs []types.Log) ([]*orm.CrossMsg, []msgHashWrapper, []*orm.RelayedMsg, error) {
|
||||
// Need use contract abi to parse event Log
|
||||
// Can only be tested after we have our contracts set up
|
||||
|
||||
var l2CrossMsg []*orm.CrossMsg
|
||||
var relayedMsgs []*orm.RelayedMsg
|
||||
var msgHashes []msgHashWrapper
|
||||
for _, vlog := range logs {
|
||||
switch vlog.Topics[0] {
|
||||
case backendabi.L2WithdrawETHSig:
|
||||
event := backendabi.DepositETH{}
|
||||
err := utils.UnpackLog(backendabi.L2ETHGatewayABI, &event, "WithdrawETH", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawETH event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ETH),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
case backendabi.L2WithdrawERC20Sig:
|
||||
event := backendabi.ERC20MessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L2StandardERC20GatewayABI, &event, "WithdrawERC20", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawERC20 event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ERC20),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
})
|
||||
case backendabi.L2WithdrawERC721Sig:
|
||||
event := backendabi.ERC721MessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L2ERC721GatewayABI, &event, "WithdrawERC721", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawERC721 event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC721),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
})
|
||||
case backendabi.L2WithdrawERC1155Sig:
|
||||
event := backendabi.ERC1155MessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L2ERC1155GatewayABI, &event, "WithdrawERC1155", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawERC1155 event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC1155),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
Amount: event.Amount.String(),
|
||||
})
|
||||
case backendabi.L2SentMessageEventSignature:
|
||||
event := backendabi.L2SentMessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L2ScrollMessengerABI, &event, "SentMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack SentMessage event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
msgHash := utils.ComputeMessageHash(event.Sender, event.Target, event.Value, event.MessageNonce, event.Message)
|
||||
msgHashes = append(msgHashes, msgHashWrapper{
|
||||
msgHash: msgHash,
|
||||
txHash: vlog.TxHash})
|
||||
case backendabi.L2RelayedMessageEventSignature:
|
||||
event := backendabi.L2RelayedMessageEvent{}
|
||||
err := utils.UnpackLog(backendabi.L2ScrollMessengerABI, &event, "RelayedMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack RelayedMessage event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
relayedMsgs = append(relayedMsgs, &orm.RelayedMsg{
|
||||
MsgHash: event.MessageHash.String(),
|
||||
Height: vlog.BlockNumber,
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, nil
|
||||
}
|
||||
|
||||
func updateL1CrossMsgMsgHash(ctx context.Context, dbTx *sqlx.Tx, database db.OrmFactory, msgHashes []msgHashWrapper) error {
|
||||
func updateL1CrossMsgMsgHash(ctx context.Context, dbTx *sqlx.Tx, database db.OrmFactory, msgHashes []utils.MsgHashWrapper) error {
|
||||
for _, msgHash := range msgHashes {
|
||||
err := database.UpdateL1CrossMsgHashDBTx(ctx, dbTx, msgHash.txHash, msgHash.msgHash)
|
||||
err := database.UpdateL1CrossMsgHashDBTx(ctx, dbTx, msgHash.TxHash, msgHash.MsgHash)
|
||||
if err != nil {
|
||||
log.Error("updateL1CrossMsgMsgHash: can not update layer1 cross msg MsgHash", "layer1 hash", msgHash.txHash, "err", err)
|
||||
log.Error("updateL1CrossMsgMsgHash: can not update layer1 cross msg MsgHash", "layer1 hash", msgHash.TxHash, "err", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateL2CrossMsgMsgHash(ctx context.Context, dbTx *sqlx.Tx, database db.OrmFactory, msgHashes []msgHashWrapper) error {
|
||||
func updateL2CrossMsgMsgHash(ctx context.Context, dbTx *sqlx.Tx, database db.OrmFactory, msgHashes []utils.MsgHashWrapper) error {
|
||||
for _, msgHash := range msgHashes {
|
||||
err := database.UpdateL2CrossMsgHashDBTx(ctx, dbTx, msgHash.txHash, msgHash.msgHash)
|
||||
err := database.UpdateL2CrossMsgHashDBTx(ctx, dbTx, msgHash.TxHash, msgHash.MsgHash)
|
||||
if err != nil {
|
||||
log.Error("updateL2CrossMsgMsgHash: can not update layer2 cross msg MsgHash", "layer2 hash", msgHash.txHash, "err", err)
|
||||
log.Error("updateL2CrossMsgMsgHash: can not update layer2 cross msg MsgHash", "layer2 hash", msgHash.TxHash, "err", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
241
bridge-history-api/cross_msg/message_proof/msg_proof_updater.go
Normal file
241
bridge-history-api/cross_msg/message_proof/msg_proof_updater.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package message_proof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"bridge-history-api/db"
|
||||
"bridge-history-api/db/orm"
|
||||
)
|
||||
|
||||
type MsgProofUpdater struct {
|
||||
ctx context.Context
|
||||
db db.OrmFactory
|
||||
withdrawTrie *WithdrawTrie
|
||||
}
|
||||
|
||||
func NewMsgProofUpdater(ctx context.Context, confirmations uint64, startBlock uint64, db db.OrmFactory) *MsgProofUpdater {
|
||||
return &MsgProofUpdater{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
withdrawTrie: NewWithdrawTrie(),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MsgProofUpdater) Start() {
|
||||
log.Info("MsgProofUpdater Start")
|
||||
m.initialize(m.ctx)
|
||||
go func() {
|
||||
tick := time.NewTicker(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
tick.Stop()
|
||||
return
|
||||
case <-tick.C:
|
||||
latestBatch, err := m.db.GetLatestRollupBatch()
|
||||
if err != nil {
|
||||
log.Warn("MsgProofUpdater: Can not get latest RollupBatch: ", "err", err)
|
||||
continue
|
||||
}
|
||||
if latestBatch == nil {
|
||||
continue
|
||||
}
|
||||
latestBatchIndexWithProof, err := m.db.GetLatestL2SentMsgBatchIndex()
|
||||
if err != nil {
|
||||
log.Error("MsgProofUpdater: Can not get latest L2SentMsgBatchIndex: ", "err", err)
|
||||
continue
|
||||
}
|
||||
var start uint64
|
||||
if latestBatchIndexWithProof < 0 {
|
||||
start = 1
|
||||
} else {
|
||||
start = uint64(latestBatchIndexWithProof) + 1
|
||||
}
|
||||
for i := start; i <= latestBatch.BatchIndex; i++ {
|
||||
batch, err := m.db.GetRollupBatchByIndex(i)
|
||||
if err != nil {
|
||||
log.Error("MsgProofUpdater: Can not get RollupBatch: ", "err", err, "index", i)
|
||||
break
|
||||
}
|
||||
// get all l2 messages in this batch
|
||||
msgs, proofs, err := m.appendL2Messages(batch.StartBlockNumber, batch.EndBlockNumber)
|
||||
if err != nil {
|
||||
log.Error("MsgProofUpdater: can not append l2messages", "startBlockNumber", batch.StartBlockNumber, "endBlockNumber", batch.EndBlockNumber, "err", err)
|
||||
break
|
||||
}
|
||||
err = m.updateMsgProof(msgs, proofs, batch.BatchIndex)
|
||||
if err != nil {
|
||||
log.Error("MsgProofUpdater: can not update msg proof", "err", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func (m *MsgProofUpdater) Stop() {
|
||||
log.Info("MsgProofUpdater Stop")
|
||||
}
|
||||
|
||||
func (m *MsgProofUpdater) initialize(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
err := m.initializeWithdrawTrie()
|
||||
if err != nil {
|
||||
log.Error("can not initialize withdraw trie", "err", err)
|
||||
// give it some time to retry
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MsgProofUpdater) initializeWithdrawTrie() error {
|
||||
var batch *orm.RollupBatch
|
||||
firstMsg, err := m.db.GetL2SentMessageByNonce(0)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return fmt.Errorf("failed to get first l2 message: %v", err)
|
||||
}
|
||||
// no l2 message
|
||||
// TO DO: check if we realy dont have l2 sent message with nonce 0
|
||||
if firstMsg == nil {
|
||||
log.Info("No first l2sentmsg in db")
|
||||
return nil
|
||||
}
|
||||
|
||||
// if no batch, return and wait for next try round
|
||||
batch, err = m.db.GetLatestRollupBatch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest batch: %v", err)
|
||||
}
|
||||
if batch == nil {
|
||||
return fmt.Errorf("no batch found")
|
||||
}
|
||||
|
||||
var batches []*orm.RollupBatch
|
||||
batchIndex := batch.BatchIndex
|
||||
for {
|
||||
var msg *orm.L2SentMsg
|
||||
msg, err = m.db.GetLatestL2SentMsgLEHeight(batch.EndBlockNumber)
|
||||
if err != nil {
|
||||
log.Warn("failed to get l2 sent message less than height", "endBlocknum", batch.EndBlockNumber, "err", err)
|
||||
}
|
||||
if msg != nil && msg.MsgProof != "" {
|
||||
log.Info("Found latest l2 sent msg with proof: ", "msg_proof", msg.MsgProof, "height", msg.Height, "msg_hash", msg.MsgHash)
|
||||
// initialize withdrawTrie
|
||||
proofBytes := common.Hex2Bytes(msg.MsgProof)
|
||||
m.withdrawTrie.Initialize(msg.Nonce, common.HexToHash(msg.MsgHash), proofBytes)
|
||||
break
|
||||
}
|
||||
|
||||
// append unprocessed batch
|
||||
batches = append(batches, batch)
|
||||
|
||||
if batchIndex == 1 {
|
||||
// otherwise overflow
|
||||
// and batchIndex 0 is not in DB
|
||||
// To Do: check if we dont have batch with index 0 in future
|
||||
break
|
||||
}
|
||||
// iterate for next batch
|
||||
batchIndex--
|
||||
|
||||
batch, err = m.db.GetRollupBatchByIndex(batchIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get block batch %v: %v", batchIndex, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Build withdraw trie with pending messages")
|
||||
for i := len(batches) - 1; i >= 0; i-- {
|
||||
b := batches[i]
|
||||
msgs, proofs, err := m.appendL2Messages(b.StartBlockNumber, b.EndBlockNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateMsgProof(msgs, proofs, b.BatchIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Info("Build withdraw trie finished")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MsgProofUpdater) updateMsgProof(msgs []*orm.L2SentMsg, proofs [][]byte, batchIndex uint64) error {
|
||||
if len(msgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// this should not happend, but double checked
|
||||
if len(msgs) != len(proofs) {
|
||||
return fmt.Errorf("illegal state: len(msgs) != len(proofs)")
|
||||
}
|
||||
dbTx, err := m.db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, msg := range msgs {
|
||||
proofHex := common.Bytes2Hex(proofs[i])
|
||||
log.Debug("updateMsgProof", "msgHash", msg.MsgHash, "batchIndex", batchIndex, "proof", proofHex)
|
||||
if dbTxErr := m.db.UpdateL2MessageProofInDBTx(m.ctx, dbTx, msg.MsgHash, proofHex, batchIndex); dbTxErr != nil {
|
||||
if err := dbTx.Rollback(); err != nil {
|
||||
log.Error("dbTx.Rollback()", "err", err)
|
||||
}
|
||||
return dbTxErr
|
||||
}
|
||||
}
|
||||
|
||||
if dbTxErr := dbTx.Commit(); dbTxErr != nil {
|
||||
if err := dbTx.Rollback(); err != nil {
|
||||
log.Error("dbTx.Rollback()", "err", err)
|
||||
}
|
||||
return dbTxErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendL2Messages will append all messages between firstBlock and lastBlock (both inclusive) to withdrawTrie and compute corresponding merkle proof of each message.
|
||||
func (m *MsgProofUpdater) appendL2Messages(firstBlock, lastBlock uint64) ([]*orm.L2SentMsg, [][]byte, error) {
|
||||
var msgProofs [][]byte
|
||||
messages, err := m.db.GetL2SentMsgMsgHashByHeightRange(firstBlock, lastBlock)
|
||||
if err != nil {
|
||||
log.Error("GetL2SentMsgMsgHashByHeightRange failed", "error", err, "firstBlock", firstBlock, "lastBlock", lastBlock)
|
||||
return messages, msgProofs, err
|
||||
}
|
||||
if len(messages) == 0 {
|
||||
return messages, msgProofs, nil
|
||||
}
|
||||
|
||||
// double check whether nonce is matched
|
||||
if messages[0].Nonce != m.withdrawTrie.NextMessageNonce {
|
||||
log.Error("L2 message nonce mismatch", "expected", m.withdrawTrie.NextMessageNonce, "found", messages[0].Nonce)
|
||||
return messages, msgProofs, fmt.Errorf("l2 message nonce mismatch, expected: %v, found: %v", m.withdrawTrie.NextMessageNonce, messages[0].Nonce)
|
||||
}
|
||||
|
||||
var hashes []common.Hash
|
||||
for _, msg := range messages {
|
||||
hashes = append(hashes, common.HexToHash(msg.MsgHash))
|
||||
}
|
||||
msgProofs = m.withdrawTrie.AppendMessages(hashes)
|
||||
|
||||
return messages, msgProofs, nil
|
||||
}
|
||||
189
bridge-history-api/cross_msg/message_proof/withdraw_trie.go
Normal file
189
bridge-history-api/cross_msg/message_proof/withdraw_trie.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package message_proof
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"bridge-history-api/utils"
|
||||
)
|
||||
|
||||
// MaxHeight is the maixium possible height of withdraw trie
|
||||
const MaxHeight = 40
|
||||
|
||||
// WithdrawTrie is an append only merkle trie
|
||||
type WithdrawTrie struct {
|
||||
// used to rebuild the merkle tree
|
||||
NextMessageNonce uint64
|
||||
|
||||
height int // current height of withdraw trie
|
||||
|
||||
branches []common.Hash
|
||||
zeroes []common.Hash
|
||||
}
|
||||
|
||||
// NewWithdrawTrie will return a new instance of WithdrawTrie
|
||||
func NewWithdrawTrie() *WithdrawTrie {
|
||||
zeroes := make([]common.Hash, MaxHeight)
|
||||
branches := make([]common.Hash, MaxHeight)
|
||||
|
||||
zeroes[0] = common.Hash{}
|
||||
for i := 1; i < MaxHeight; i++ {
|
||||
zeroes[i] = utils.Keccak2(zeroes[i-1], zeroes[i-1])
|
||||
}
|
||||
|
||||
return &WithdrawTrie{
|
||||
zeroes: zeroes,
|
||||
branches: branches,
|
||||
height: -1,
|
||||
NextMessageNonce: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize will initialize the merkle trie with rightest leaf node
|
||||
func (w *WithdrawTrie) Initialize(currentMessageNonce uint64, msgHash common.Hash, proofBytes []byte) {
|
||||
proof := DecodeBytesToMerkleProof(proofBytes)
|
||||
branches := RecoverBranchFromProof(proof, currentMessageNonce, msgHash)
|
||||
w.height = len(proof)
|
||||
w.branches = branches
|
||||
w.NextMessageNonce = currentMessageNonce + 1
|
||||
}
|
||||
|
||||
// AppendMessages appends a list of new messages as leaf nodes to the rightest of the tree and returns the proofs for all messages.
|
||||
func (w *WithdrawTrie) AppendMessages(hashes []common.Hash) [][]byte {
|
||||
length := len(hashes)
|
||||
if length == 0 {
|
||||
return make([][]byte, 0)
|
||||
}
|
||||
|
||||
cache := make([]map[uint64]common.Hash, MaxHeight)
|
||||
for h := 0; h < MaxHeight; h++ {
|
||||
cache[h] = make(map[uint64]common.Hash)
|
||||
}
|
||||
|
||||
// cache all branches will be used later.
|
||||
if w.NextMessageNonce != 0 {
|
||||
index := w.NextMessageNonce
|
||||
for h := 0; h <= w.height; h++ {
|
||||
if index%2 == 1 {
|
||||
// right child, `w.branches[h]` is the corresponding left child
|
||||
// the index of left child should be `index ^ 1`.
|
||||
cache[h][index^1] = w.branches[h]
|
||||
}
|
||||
index >>= 1
|
||||
}
|
||||
}
|
||||
// cache all new leaves
|
||||
for i := 0; i < length; i++ {
|
||||
cache[0][w.NextMessageNonce+uint64(i)] = hashes[i]
|
||||
}
|
||||
|
||||
// build withdraw trie with new hashes
|
||||
minIndex := w.NextMessageNonce
|
||||
maxIndex := w.NextMessageNonce + uint64(length) - 1
|
||||
for h := 0; maxIndex > 0; h++ {
|
||||
if minIndex%2 == 1 {
|
||||
minIndex--
|
||||
}
|
||||
if maxIndex%2 == 0 {
|
||||
cache[h][maxIndex^1] = w.zeroes[h]
|
||||
}
|
||||
for i := minIndex; i <= maxIndex; i += 2 {
|
||||
cache[h+1][i>>1] = utils.Keccak2(cache[h][i], cache[h][i^1])
|
||||
}
|
||||
minIndex >>= 1
|
||||
maxIndex >>= 1
|
||||
}
|
||||
|
||||
// update branches using hashes one by one
|
||||
for i := 0; i < length; i++ {
|
||||
proof := UpdateBranchWithNewMessage(w.zeroes, w.branches, w.NextMessageNonce, hashes[i])
|
||||
w.NextMessageNonce++
|
||||
w.height = len(proof)
|
||||
}
|
||||
|
||||
proofs := make([][]byte, length)
|
||||
// retrieve merkle proof from cache
|
||||
for i := 0; i < length; i++ {
|
||||
index := w.NextMessageNonce + uint64(i) - uint64(length)
|
||||
var merkleProof []common.Hash
|
||||
for h := 0; h < w.height; h++ {
|
||||
merkleProof = append(merkleProof, cache[h][index^1])
|
||||
index >>= 1
|
||||
}
|
||||
proofs[i] = EncodeMerkleProofToBytes(merkleProof)
|
||||
}
|
||||
|
||||
return proofs
|
||||
}
|
||||
|
||||
// MessageRoot return the current root hash of withdraw trie.
|
||||
func (w *WithdrawTrie) MessageRoot() common.Hash {
|
||||
if w.height == -1 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return w.branches[w.height]
|
||||
}
|
||||
|
||||
// DecodeBytesToMerkleProof transfer byte array to bytes32 array. The caller should make sure the length is matched.
|
||||
func DecodeBytesToMerkleProof(proofBytes []byte) []common.Hash {
|
||||
proof := make([]common.Hash, len(proofBytes)/32)
|
||||
for i := 0; i < len(proofBytes); i += 32 {
|
||||
proof[i/32] = common.BytesToHash(proofBytes[i : i+32])
|
||||
}
|
||||
return proof
|
||||
}
|
||||
|
||||
// EncodeMerkleProofToBytes transfer byte32 array to byte array by concatenation.
|
||||
func EncodeMerkleProofToBytes(proof []common.Hash) []byte {
|
||||
var proofBytes []byte
|
||||
for i := 0; i < len(proof); i++ {
|
||||
proofBytes = append(proofBytes, proof[i][:]...)
|
||||
}
|
||||
return proofBytes
|
||||
}
|
||||
|
||||
// UpdateBranchWithNewMessage update the branches to latest with new message and return the merkle proof for the message.
|
||||
func UpdateBranchWithNewMessage(zeroes []common.Hash, branches []common.Hash, index uint64, msgHash common.Hash) []common.Hash {
|
||||
root := msgHash
|
||||
var merkleProof []common.Hash
|
||||
var height uint64
|
||||
for height = 0; index > 0; height++ {
|
||||
if index%2 == 0 {
|
||||
// it may be used in next round.
|
||||
branches[height] = root
|
||||
merkleProof = append(merkleProof, zeroes[height])
|
||||
// it's a left child, the right child must be null
|
||||
root = utils.Keccak2(root, zeroes[height])
|
||||
} else {
|
||||
// it's a right child, use previously computed hash
|
||||
root = utils.Keccak2(branches[height], root)
|
||||
merkleProof = append(merkleProof, branches[height])
|
||||
}
|
||||
index >>= 1
|
||||
}
|
||||
branches[height] = root
|
||||
return merkleProof
|
||||
}
|
||||
|
||||
// RecoverBranchFromProof will recover latest branches from merkle proof and message hash
|
||||
func RecoverBranchFromProof(proof []common.Hash, index uint64, msgHash common.Hash) []common.Hash {
|
||||
branches := make([]common.Hash, 64)
|
||||
root := msgHash
|
||||
var height uint64
|
||||
for height = 0; index > 0; height++ {
|
||||
if index%2 == 0 {
|
||||
branches[height] = root
|
||||
// it's a left child, the right child must be null
|
||||
root = utils.Keccak2(root, proof[height])
|
||||
} else {
|
||||
// it's a right child, use previously computed hash
|
||||
branches[height] = proof[height]
|
||||
root = utils.Keccak2(proof[height], root)
|
||||
}
|
||||
index >>= 1
|
||||
}
|
||||
branches[height] = root
|
||||
for height++; height < 64; height++ {
|
||||
branches[height] = common.Hash{}
|
||||
}
|
||||
return branches
|
||||
}
|
||||
212
bridge-history-api/cross_msg/message_proof/withdraw_trie_test.go
Normal file
212
bridge-history-api/cross_msg/message_proof/withdraw_trie_test.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package message_proof
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"bridge-history-api/utils"
|
||||
)
|
||||
|
||||
func TestUpdateBranchWithNewMessage(t *testing.T) {
|
||||
zeroes := make([]common.Hash, 64)
|
||||
branches := make([]common.Hash, 64)
|
||||
zeroes[0] = common.Hash{}
|
||||
for i := 1; i < 64; i++ {
|
||||
zeroes[i] = utils.Keccak2(zeroes[i-1], zeroes[i-1])
|
||||
}
|
||||
|
||||
UpdateBranchWithNewMessage(zeroes, branches, 0, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
if branches[0] != common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001") {
|
||||
t.Fatalf("Invalid root, want %s, got %s", "0x0000000000000000000000000000000000000000000000000000000000000001", branches[0].Hex())
|
||||
}
|
||||
|
||||
UpdateBranchWithNewMessage(zeroes, branches, 1, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002"))
|
||||
if branches[1] != common.HexToHash("0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0") {
|
||||
t.Fatalf("Invalid root, want %s, got %s", "0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0", branches[1].Hex())
|
||||
}
|
||||
|
||||
UpdateBranchWithNewMessage(zeroes, branches, 2, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003"))
|
||||
if branches[2] != common.HexToHash("0x222ff5e0b5877792c2bc1670e2ccd0c2c97cd7bb1672a57d598db05092d3d72c") {
|
||||
t.Fatalf("Invalid root, want %s, got %s", "0x222ff5e0b5877792c2bc1670e2ccd0c2c97cd7bb1672a57d598db05092d3d72c", branches[2].Hex())
|
||||
}
|
||||
|
||||
UpdateBranchWithNewMessage(zeroes, branches, 3, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000004"))
|
||||
if branches[2] != common.HexToHash("0xa9bb8c3f1f12e9aa903a50c47f314b57610a3ab32f2d463293f58836def38d36") {
|
||||
t.Fatalf("Invalid root, want %s, got %s", "0xa9bb8c3f1f12e9aa903a50c47f314b57610a3ab32f2d463293f58836def38d36", branches[2].Hex())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeEncodeMerkleProof(t *testing.T) {
|
||||
proof := DecodeBytesToMerkleProof(common.Hex2Bytes("2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d49012ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d49022ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d49032ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4904"))
|
||||
if len(proof) != 4 {
|
||||
t.Fatalf("proof length mismatch, want %d, got %d", 4, len(proof))
|
||||
}
|
||||
if proof[0] != common.HexToHash("0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4901") {
|
||||
t.Fatalf("proof[0] mismatch, want %s, got %s", "0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4901", proof[0].Hex())
|
||||
}
|
||||
if proof[1] != common.HexToHash("0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4902") {
|
||||
t.Fatalf("proof[1] mismatch, want %s, got %s", "0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4902", proof[0].Hex())
|
||||
}
|
||||
if proof[2] != common.HexToHash("0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4903") {
|
||||
t.Fatalf("proof[2] mismatch, want %s, got %s", "0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4903", proof[0].Hex())
|
||||
}
|
||||
if proof[3] != common.HexToHash("0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4904") {
|
||||
t.Fatalf("proof[3] mismatch, want %s, got %s", "0x2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4904", proof[0].Hex())
|
||||
}
|
||||
|
||||
bytes := EncodeMerkleProofToBytes(proof)
|
||||
if common.Bytes2Hex(bytes) != "2ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d49012ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d49022ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d49032ebffc1a6671c51e30777a680904b103992630ec995b6e6ff76a04d5259d4904" {
|
||||
t.Fatalf("wrong encoded bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecoverBranchFromProof(t *testing.T) {
|
||||
zeroes := make([]common.Hash, 64)
|
||||
branches := make([]common.Hash, 64)
|
||||
zeroes[0] = common.Hash{}
|
||||
for i := 1; i < 64; i++ {
|
||||
zeroes[i] = utils.Keccak2(zeroes[i-1], zeroes[i-1])
|
||||
}
|
||||
|
||||
proof := UpdateBranchWithNewMessage(zeroes, branches, 0, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
tmpBranches := RecoverBranchFromProof(proof, 0, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
for i := 0; i < 64; i++ {
|
||||
if tmpBranches[i] != branches[i] {
|
||||
t.Fatalf("Invalid branch, want %s, got %s", branches[i].Hex(), tmpBranches[i].Hex())
|
||||
}
|
||||
}
|
||||
|
||||
proof = UpdateBranchWithNewMessage(zeroes, branches, 1, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002"))
|
||||
tmpBranches = RecoverBranchFromProof(proof, 1, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002"))
|
||||
for i := 0; i < 64; i++ {
|
||||
if tmpBranches[i] != branches[i] {
|
||||
t.Fatalf("Invalid branch, want %s, got %s", branches[i].Hex(), tmpBranches[i].Hex())
|
||||
}
|
||||
}
|
||||
|
||||
proof = UpdateBranchWithNewMessage(zeroes, branches, 2, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003"))
|
||||
tmpBranches = RecoverBranchFromProof(proof, 2, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003"))
|
||||
for i := 0; i < 64; i++ {
|
||||
if tmpBranches[i] != branches[i] {
|
||||
t.Fatalf("Invalid branch, want %s, got %s", branches[i].Hex(), tmpBranches[i].Hex())
|
||||
}
|
||||
}
|
||||
|
||||
proof = UpdateBranchWithNewMessage(zeroes, branches, 3, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000004"))
|
||||
tmpBranches = RecoverBranchFromProof(proof, 3, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000004"))
|
||||
for i := 0; i < 64; i++ {
|
||||
if tmpBranches[i] != branches[i] {
|
||||
t.Fatalf("Invalid branch, want %s, got %s", branches[i].Hex(), tmpBranches[i].Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithdrawTrieOneByOne(t *testing.T) {
|
||||
for initial := 0; initial < 128; initial++ {
|
||||
withdrawTrie := NewWithdrawTrie()
|
||||
var hashes []common.Hash
|
||||
for i := 0; i < initial; i++ {
|
||||
hash := common.BigToHash(big.NewInt(int64(i + 1)))
|
||||
hashes = append(hashes, hash)
|
||||
withdrawTrie.AppendMessages([]common.Hash{
|
||||
hash,
|
||||
})
|
||||
}
|
||||
|
||||
for i := initial; i < 128; i++ {
|
||||
hash := common.BigToHash(big.NewInt(int64(i + 1)))
|
||||
hashes = append(hashes, hash)
|
||||
expectedRoot := computeMerkleRoot(hashes)
|
||||
proofBytes := withdrawTrie.AppendMessages([]common.Hash{
|
||||
hash,
|
||||
})
|
||||
assert.Equal(t, withdrawTrie.NextMessageNonce, uint64(i+1))
|
||||
assert.Equal(t, expectedRoot.String(), withdrawTrie.MessageRoot().String())
|
||||
proof := DecodeBytesToMerkleProof(proofBytes[0])
|
||||
verifiedRoot := verifyMerkleProof(uint64(i), hash, proof)
|
||||
assert.Equal(t, expectedRoot.String(), verifiedRoot.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithdrawTrieMultiple(t *testing.T) {
|
||||
var expectedRoots []common.Hash
|
||||
|
||||
{
|
||||
var hashes []common.Hash
|
||||
for i := 0; i < 128; i++ {
|
||||
hash := common.BigToHash(big.NewInt(int64(i + 1)))
|
||||
hashes = append(hashes, hash)
|
||||
expectedRoots = append(expectedRoots, computeMerkleRoot(hashes))
|
||||
}
|
||||
}
|
||||
|
||||
for initial := 0; initial < 100; initial++ {
|
||||
var hashes []common.Hash
|
||||
for i := 0; i < initial; i++ {
|
||||
hash := common.BigToHash(big.NewInt(int64(i + 1)))
|
||||
hashes = append(hashes, hash)
|
||||
}
|
||||
|
||||
for finish := initial; finish < 100; finish++ {
|
||||
withdrawTrie := NewWithdrawTrie()
|
||||
withdrawTrie.AppendMessages(hashes)
|
||||
|
||||
var newHashes []common.Hash
|
||||
for i := initial; i <= finish; i++ {
|
||||
hash := common.BigToHash(big.NewInt(int64(i + 1)))
|
||||
newHashes = append(newHashes, hash)
|
||||
}
|
||||
proofBytes := withdrawTrie.AppendMessages(newHashes)
|
||||
assert.Equal(t, withdrawTrie.NextMessageNonce, uint64(finish+1))
|
||||
assert.Equal(t, expectedRoots[finish].String(), withdrawTrie.MessageRoot().String())
|
||||
|
||||
for i := initial; i <= finish; i++ {
|
||||
hash := common.BigToHash(big.NewInt(int64(i + 1)))
|
||||
proof := DecodeBytesToMerkleProof(proofBytes[i-initial])
|
||||
verifiedRoot := verifyMerkleProof(uint64(i), hash, proof)
|
||||
assert.Equal(t, expectedRoots[finish].String(), verifiedRoot.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyMerkleProof(index uint64, leaf common.Hash, proof []common.Hash) common.Hash {
|
||||
root := leaf
|
||||
for _, h := range proof {
|
||||
if index%2 == 0 {
|
||||
root = utils.Keccak2(root, h)
|
||||
} else {
|
||||
root = utils.Keccak2(h, root)
|
||||
}
|
||||
index >>= 1
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func computeMerkleRoot(hashes []common.Hash) common.Hash {
|
||||
if len(hashes) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
zeroHash := common.Hash{}
|
||||
for {
|
||||
if len(hashes) == 1 {
|
||||
break
|
||||
}
|
||||
var newHashes []common.Hash
|
||||
for i := 0; i < len(hashes); i += 2 {
|
||||
if i+1 < len(hashes) {
|
||||
newHashes = append(newHashes, utils.Keccak2(hashes[i], hashes[i+1]))
|
||||
} else {
|
||||
newHashes = append(newHashes, utils.Keccak2(hashes[i], zeroHash))
|
||||
}
|
||||
}
|
||||
hashes = newHashes
|
||||
zeroHash = utils.Keccak2(zeroHash, zeroHash)
|
||||
}
|
||||
return hashes[0]
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package cross_msg_test
|
||||
|
||||
import (
|
||||
"bridge-history-api/cross_msg"
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"testing"
|
||||
@@ -9,6 +8,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"bridge-history-api/cross_msg"
|
||||
)
|
||||
|
||||
func TestMergeIntoList(t *testing.T) {
|
||||
|
||||
@@ -94,6 +94,11 @@ func L2ReorgHandling(ctx context.Context, reorgHeight int64, db db.OrmFactory) e
|
||||
dbTx.Rollback()
|
||||
log.Crit("delete l2 relayed hash from height", "height", reorgHeight, "err", err)
|
||||
}
|
||||
err = db.DeleteL2SentMsgAfterHeightDBTx(dbTx, reorgHeight)
|
||||
if err != nil {
|
||||
dbTx.Rollback()
|
||||
log.Crit("delete l2 sent msg from height", "height", reorgHeight, "err", err)
|
||||
}
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
dbTx.Rollback()
|
||||
|
||||
@@ -2,23 +2,24 @@
|
||||
-- +goose StatementBegin
|
||||
create table cross_message
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
msg_hash VARCHAR NOT NULL DEFAULT '',
|
||||
height BIGINT NOT NULL,
|
||||
sender VARCHAR NOT NULL,
|
||||
target VARCHAR NOT NULL,
|
||||
amount VARCHAR NOT NULL,
|
||||
layer1_hash VARCHAR NOT NULL DEFAULT '',
|
||||
layer2_hash VARCHAR NOT NULL DEFAULT '',
|
||||
layer1_token VARCHAR NOT NULL DEFAULT '',
|
||||
layer2_token VARCHAR NOT NULL DEFAULT '',
|
||||
token_id BIGINT NOT NULL DEFAULT 0,
|
||||
asset SMALLINT NOT NULL,
|
||||
msg_type SMALLINT NOT NULL,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP(0) DEFAULT NULL
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
msg_hash VARCHAR NOT NULL DEFAULT '',
|
||||
height BIGINT NOT NULL,
|
||||
sender VARCHAR NOT NULL,
|
||||
target VARCHAR NOT NULL,
|
||||
amount VARCHAR NOT NULL,
|
||||
layer1_hash VARCHAR NOT NULL DEFAULT '',
|
||||
layer2_hash VARCHAR NOT NULL DEFAULT '',
|
||||
layer1_token VARCHAR NOT NULL DEFAULT '',
|
||||
layer2_token VARCHAR NOT NULL DEFAULT '',
|
||||
token_id BIGINT NOT NULL DEFAULT 0,
|
||||
asset SMALLINT NOT NULL,
|
||||
msg_type SMALLINT NOT NULL,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
block_timestamp TIMESTAMP(0) DEFAULT NULL,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP(0) DEFAULT NULL
|
||||
);
|
||||
|
||||
comment
|
||||
@@ -48,20 +49,20 @@ CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON cross_message FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
CREATE OR REPLACE FUNCTION delete_at_trigger()
|
||||
CREATE OR REPLACE FUNCTION deleted_at_trigger()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
|
||||
UPDATE cross_message SET delete_at = NOW() WHERE id = NEW.id;
|
||||
UPDATE cross_message SET deleted_at = NOW() WHERE id = NEW.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER delete_at_trigger
|
||||
CREATE TRIGGER deleted_at_trigger
|
||||
AFTER UPDATE ON cross_message
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION delete_at_trigger();
|
||||
EXECUTE FUNCTION deleted_at_trigger();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -31,20 +31,20 @@ CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON relayed_msg FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
CREATE OR REPLACE FUNCTION delete_at_trigger()
|
||||
CREATE OR REPLACE FUNCTION deleted_at_trigger()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
|
||||
UPDATE relayed_msg SET delete_at = NOW() WHERE id = NEW.id;
|
||||
UPDATE relayed_msg SET deleted_at = NOW() WHERE id = NEW.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER delete_at_trigger
|
||||
CREATE TRIGGER deleted_at_trigger
|
||||
AFTER UPDATE ON relayed_msg
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION delete_at_trigger();
|
||||
EXECUTE FUNCTION deleted_at_trigger();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
create table l2_sent_msg
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
sender VARCHAR NOT NULL,
|
||||
target VARCHAR NOT NULL,
|
||||
value VARCHAR NOT NULL,
|
||||
msg_hash VARCHAR NOT NULL,
|
||||
height BIGINT NOT NULL,
|
||||
nonce BIGINT NOT NULL,
|
||||
batch_index BIGINT NOT NULL DEFAULT 0,
|
||||
msg_proof TEXT NOT NULL DEFAULT '',
|
||||
msg_data TEXT NOT NULL DEFAULT '',
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP(0) DEFAULT NULL
|
||||
);
|
||||
|
||||
comment
|
||||
on column l2_sent_msg.is_deleted is 'NotDeleted, Deleted';
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON l2_sent_msg FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
CREATE OR REPLACE FUNCTION deleted_at_trigger()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
|
||||
UPDATE l2_sent_msg SET deleted_at = NOW() WHERE id = NEW.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER deleted_at_trigger
|
||||
AFTER UPDATE ON l2_sent_msg
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION deleted_at_trigger();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists l2_sent_msg;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,49 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
create table rollup_batch
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
batch_index BIGINT NOT NULL,
|
||||
commit_height BIGINT NOT NULL,
|
||||
start_block_number BIGINT NOT NULL,
|
||||
end_block_number BIGINT NOT NULL,
|
||||
batch_hash VARCHAR NOT NULL,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP(0) DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON rollup_batch FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
CREATE OR REPLACE FUNCTION deleted_at_trigger()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
|
||||
UPDATE rollup_batch SET deleted_at = NOW() WHERE id = NEW.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER deleted_at_trigger
|
||||
AFTER UPDATE ON rollup_batch
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION deleted_at_trigger();
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists rollup_batch;
|
||||
-- +goose StatementEnd
|
||||
79
bridge-history-api/db/orm/batch.go
Normal file
79
bridge-history-api/db/orm/batch.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type rollupBatchOrm struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
type RollupBatch struct {
|
||||
ID uint64 `json:"id" db:"id"`
|
||||
BatchIndex uint64 `json:"batch_index" db:"batch_index"`
|
||||
BatchHash string `json:"batch_hash" db:"batch_hash"`
|
||||
CommitHeight uint64 `json:"commit_height" db:"commit_height"`
|
||||
StartBlockNumber uint64 `json:"start_block_number" db:"start_block_number"`
|
||||
EndBlockNumber uint64 `json:"end_block_number" db:"end_block_number"`
|
||||
}
|
||||
|
||||
// NewRollupBatchOrm create an NewRollupBatchOrm instance
|
||||
func NewRollupBatchOrm(db *sqlx.DB) RollupBatchOrm {
|
||||
return &rollupBatchOrm{db: db}
|
||||
}
|
||||
|
||||
func (b *rollupBatchOrm) BatchInsertRollupBatchDBTx(dbTx *sqlx.Tx, batches []*RollupBatch) error {
|
||||
if len(batches) == 0 {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
batchMaps := make([]map[string]interface{}, len(batches))
|
||||
for i, batch := range batches {
|
||||
batchMaps[i] = map[string]interface{}{
|
||||
"commit_height": batch.CommitHeight,
|
||||
"batch_index": batch.BatchIndex,
|
||||
"batch_hash": batch.BatchHash,
|
||||
"start_block_number": batch.StartBlockNumber,
|
||||
"end_block_number": batch.EndBlockNumber,
|
||||
}
|
||||
var exists bool
|
||||
err = dbTx.QueryRow(`SELECT EXISTS(SELECT 1 FROM rollup_batch WHERE batch_index = $1 AND NOT is_deleted)`, batch.BatchIndex).Scan(&exists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("BatchInsertRollupBatchDBTx: batch index %v already exists at height %v", batch.BatchIndex, batch.CommitHeight)
|
||||
}
|
||||
}
|
||||
_, err = dbTx.NamedExec(`insert into rollup_batch(commit_height, batch_index, batch_hash, start_block_number, end_block_number) values(:commit_height, :batch_index, :batch_hash, :start_block_number, :end_block_number);`, batchMaps)
|
||||
if err != nil {
|
||||
log.Error("BatchInsertRollupBatchDBTx: failed to insert batch event msgs", "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *rollupBatchOrm) GetLatestRollupBatch() (*RollupBatch, error) {
|
||||
result := &RollupBatch{}
|
||||
row := b.db.QueryRowx(`SELECT id, batch_index, commit_height, batch_hash, start_block_number, end_block_number FROM rollup_batch ORDER BY batch_index DESC LIMIT 1;`)
|
||||
if err := row.StructScan(result); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *rollupBatchOrm) GetRollupBatchByIndex(index uint64) (*RollupBatch, error) {
|
||||
result := &RollupBatch{}
|
||||
row := b.db.QueryRowx(`SELECT id, batch_index, batch_hash, commit_height, start_block_number, end_block_number FROM rollup_batch WHERE batch_index = $1;`, index)
|
||||
if err := row.StructScan(result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -54,6 +54,7 @@ type CrossMsg struct {
|
||||
Asset int `json:"asset" db:"asset"`
|
||||
MsgType int `json:"msg_type" db:"msg_type"`
|
||||
IsDeleted bool `json:"is_deleted" db:"is_deleted"`
|
||||
Timestamp *time.Time `json:"timestamp" db:"block_timestamp"`
|
||||
CreatedAt *time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at" db:"updated_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at" db:"deleted_at"`
|
||||
@@ -71,23 +72,27 @@ type L1CrossMsgOrm interface {
|
||||
GetL1CrossMsgByHash(l1Hash common.Hash) (*CrossMsg, error)
|
||||
GetL1CrossMsgsByAddress(sender common.Address) ([]*CrossMsg, error)
|
||||
BatchInsertL1CrossMsgDBTx(dbTx *sqlx.Tx, messages []*CrossMsg) error
|
||||
// UpdateL1CrossMsgHash invoked when SentMessage event is received
|
||||
// UpdateL1CrossMsgHashDBTx invoked when SentMessage event is received
|
||||
UpdateL1CrossMsgHashDBTx(ctx context.Context, dbTx *sqlx.Tx, l1Hash, msgHash common.Hash) error
|
||||
UpdateL1CrossMsgHash(ctx context.Context, l1Hash, msgHash common.Hash) error
|
||||
GetLatestL1ProcessedHeight() (int64, error)
|
||||
DeleteL1CrossMsgAfterHeightDBTx(dbTx *sqlx.Tx, height int64) error
|
||||
UpdateL1BlockTimestamp(height uint64, timestamp time.Time) error
|
||||
GetL1EarliestNoBlockTimestampHeight() (uint64, error)
|
||||
}
|
||||
|
||||
// L2CrossMsgOrm provides operations on l2_cross_message table
|
||||
// L2CrossMsgOrm provides operations on cross_message table
|
||||
type L2CrossMsgOrm interface {
|
||||
GetL2CrossMsgByHash(l2Hash common.Hash) (*CrossMsg, error)
|
||||
GetL2CrossMsgByAddress(sender common.Address) ([]*CrossMsg, error)
|
||||
BatchInsertL2CrossMsgDBTx(dbTx *sqlx.Tx, messages []*CrossMsg) error
|
||||
// UpdateL2CrossMsgHash invoked when SentMessage event is received
|
||||
// UpdateL2CrossMsgHashDBTx invoked when SentMessage event is received
|
||||
UpdateL2CrossMsgHashDBTx(ctx context.Context, dbTx *sqlx.Tx, l2Hash, msgHash common.Hash) error
|
||||
UpdateL2CrossMsgHash(ctx context.Context, l2Hash, msgHash common.Hash) error
|
||||
GetLatestL2ProcessedHeight() (int64, error)
|
||||
DeleteL2CrossMsgFromHeightDBTx(dbTx *sqlx.Tx, height int64) error
|
||||
UpdateL2BlockTimestamp(height uint64, timestamp time.Time) error
|
||||
GetL2EarliestNoBlockTimestampHeight() (uint64, error)
|
||||
}
|
||||
|
||||
type RelayedMsgOrm interface {
|
||||
@@ -98,3 +103,21 @@ type RelayedMsgOrm interface {
|
||||
DeleteL1RelayedHashAfterHeightDBTx(dbTx *sqlx.Tx, height int64) error
|
||||
DeleteL2RelayedHashAfterHeightDBTx(dbTx *sqlx.Tx, height int64) error
|
||||
}
|
||||
|
||||
type L2SentMsgOrm interface {
|
||||
BatchInsertL2SentMsgDBTx(dbTx *sqlx.Tx, messages []*L2SentMsg) error
|
||||
GetL2SentMsgByHash(l2Hash string) (*L2SentMsg, error)
|
||||
GetLatestSentMsgHeightOnL2() (int64, error)
|
||||
GetL2SentMessageByNonce(nonce uint64) (*L2SentMsg, error)
|
||||
GetLatestL2SentMsgLEHeight(endBlockNumber uint64) (*L2SentMsg, error)
|
||||
GetL2SentMsgMsgHashByHeightRange(startHeight, endHeight uint64) ([]*L2SentMsg, error)
|
||||
UpdateL2MessageProofInDBTx(ctx context.Context, dbTx *sqlx.Tx, msgHash string, proof string, batch_index uint64) error
|
||||
GetLatestL2SentMsgBatchIndex() (int64, error)
|
||||
DeleteL2SentMsgAfterHeightDBTx(dbTx *sqlx.Tx, height int64) error
|
||||
}
|
||||
|
||||
type RollupBatchOrm interface {
|
||||
GetLatestRollupBatch() (*RollupBatch, error)
|
||||
GetRollupBatchByIndex(index uint64) (*RollupBatch, error)
|
||||
BatchInsertRollupBatchDBTx(dbTx *sqlx.Tx, messages []*RollupBatch) error
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -70,14 +72,22 @@ func (l *l1CrossMsgOrm) BatchInsertL1CrossMsgDBTx(dbTx *sqlx.Tx, messages []*Cro
|
||||
"token_id": msg.TokenID,
|
||||
"msg_type": Layer1Msg,
|
||||
}
|
||||
|
||||
_, err = dbTx.NamedExec(`insert into cross_message(height, sender, target, asset, layer1_hash, layer1_token, layer2_token, token_id, amount, msg_type) select :height, :sender, :target, :asset, :layer1_hash, :layer1_token, :layer2_token, :token_id, :amount, :msg_type WHERE NOT EXISTS (SELECT 1 FROM cross_message WHERE layer1_hash = :layer1_hash AND NOT is_deleted);`, messageMaps[i])
|
||||
var exists bool
|
||||
err = dbTx.QueryRow(`SELECT EXISTS(SELECT 1 FROM cross_message WHERE layer1_hash = $1 AND NOT is_deleted)`, msg.Layer1Hash).Scan(&exists)
|
||||
if err != nil {
|
||||
log.Error("BatchInsertL1CrossMsgDBTx: failed to insert l1 cross msgs", "l1hashes", msg.Layer1Hash, "heights", msg.Height, "err", err)
|
||||
break
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("BatchInsertL1CrossMsgDBTx: l1 cross msg layer1Hash %v already exists at height %v", msg.Layer1Hash, msg.Height)
|
||||
}
|
||||
}
|
||||
return err
|
||||
_, err = dbTx.NamedExec(`insert into cross_message(height, sender, target, asset, layer1_hash, layer1_token, layer2_token, token_id, amount, msg_type) values(:height, :sender, :target, :asset, :layer1_hash, :layer1_token, :layer2_token, :token_id, :amount, :msg_type);`, messageMaps)
|
||||
if err != nil {
|
||||
log.Error("BatchInsertL1CrossMsgDBTx: failed to insert l1 cross msgs", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateL1CrossMsgHashDBTx update l1 cross msg hash in db, no need to check msg_type since layer1_hash wont be empty if its layer1 msg
|
||||
@@ -118,3 +128,22 @@ func (l *l1CrossMsgOrm) DeleteL1CrossMsgAfterHeightDBTx(dbTx *sqlx.Tx, height in
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *l1CrossMsgOrm) UpdateL1BlockTimestamp(height uint64, timestamp time.Time) error {
|
||||
if _, err := l.db.Exec(`UPDATE cross_message SET block_timestamp = $1 where height = $2 AND msg_type = $3 AND NOT is_deleted`, timestamp, height, Layer1Msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *l1CrossMsgOrm) GetL1EarliestNoBlockTimestampHeight() (uint64, error) {
|
||||
row := l.db.QueryRowx(`SELECT height FROM cross_message WHERE block_timestamp IS NULL AND msg_type = $1 AND NOT is_deleted ORDER BY height ASC LIMIT 1;`, Layer1Msg)
|
||||
var result uint64
|
||||
if err := row.Scan(&result); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -21,7 +23,7 @@ func NewL2CrossMsgOrm(db *sqlx.DB) L2CrossMsgOrm {
|
||||
|
||||
func (l *l2CrossMsgOrm) GetL2CrossMsgByHash(l2Hash common.Hash) (*CrossMsg, error) {
|
||||
result := &CrossMsg{}
|
||||
row := l.db.QueryRowx(`SELECT * FROM l2_cross_message WHERE layer2_hash = $1 AND NOT is_deleted;`, l2Hash.String())
|
||||
row := l.db.QueryRowx(`SELECT * FROM cross_message WHERE layer2_hash = $1 AND NOT is_deleted;`, l2Hash.String())
|
||||
if err := row.StructScan(result); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
@@ -31,7 +33,7 @@ func (l *l2CrossMsgOrm) GetL2CrossMsgByHash(l2Hash common.Hash) (*CrossMsg, erro
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetL2CrossMsgsByAddress returns all layer2 cross messages under given address
|
||||
// GetL2CrossMsgByAddress returns all layer2 cross messages under given address
|
||||
// Warning: return empty slice if no data found
|
||||
func (l *l2CrossMsgOrm) GetL2CrossMsgByAddress(sender common.Address) ([]*CrossMsg, error) {
|
||||
var results []*CrossMsg
|
||||
@@ -83,14 +85,21 @@ func (l *l2CrossMsgOrm) BatchInsertL2CrossMsgDBTx(dbTx *sqlx.Tx, messages []*Cro
|
||||
"amount": msg.Amount,
|
||||
"msg_type": Layer2Msg,
|
||||
}
|
||||
|
||||
_, err = dbTx.NamedExec(`insert into cross_message(height, sender, target, asset, layer2_hash, layer1_token, layer2_token, token_id, amount, msg_type) select :height, :sender, :target, :asset, :layer2_hash, :layer1_token, :layer2_token, :token_id, :amount, :msg_type WHERE NOT EXISTS (SELECT 1 FROM cross_message WHERE layer2_hash = :layer2_hash AND NOT is_deleted);`, messageMaps[i])
|
||||
var exists bool
|
||||
err = dbTx.QueryRow(`SELECT EXISTS(SELECT 1 FROM cross_message WHERE layer2_hash = $1 AND NOT is_deleted)`, msg.Layer2Hash).Scan(&exists)
|
||||
if err != nil {
|
||||
log.Error("BatchInsertL2CrossMsgDBTx: failed to insert l2 cross msgs", "layer2hash", msg.Layer2Hash, "heights", msg.Height, "err", err)
|
||||
break
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("BatchInsertL2CrossMsgDBTx: l2 cross msg layer2Hash %v already exists at height %v", msg.Layer2Hash, msg.Height)
|
||||
}
|
||||
}
|
||||
return err
|
||||
_, err = dbTx.NamedExec(`insert into cross_message(height, sender, target, asset, layer2_hash, layer1_token, layer2_token, token_id, amount, msg_type) values(:height, :sender, :target, :asset, :layer2_hash, :layer1_token, :layer2_token, :token_id, :amount, :msg_type);`, messageMaps)
|
||||
if err != nil {
|
||||
log.Error("BatchInsertL2CrossMsgDBTx: failed to insert l2 cross msgs", "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *l2CrossMsgOrm) UpdateL2CrossMsgHashDBTx(ctx context.Context, dbTx *sqlx.Tx, l2Hash, msgHash common.Hash) error {
|
||||
@@ -121,3 +130,22 @@ func (l *l2CrossMsgOrm) GetLatestL2ProcessedHeight() (int64, error) {
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (l *l2CrossMsgOrm) UpdateL2BlockTimestamp(height uint64, timestamp time.Time) error {
|
||||
if _, err := l.db.Exec(`UPDATE cross_message SET block_timestamp = $1 where height = $2 AND msg_type = $3 AND NOT is_deleted`, timestamp, height, Layer2Msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *l2CrossMsgOrm) GetL2EarliestNoBlockTimestampHeight() (uint64, error) {
|
||||
row := l.db.QueryRowx(`SELECT height FROM cross_message WHERE block_timestamp IS NULL AND msg_type = $1 AND NOT is_deleted ORDER BY height ASC LIMIT 1;`, Layer2Msg)
|
||||
var result uint64
|
||||
if err := row.Scan(&result); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
159
bridge-history-api/db/orm/l2_sent_msg.go
Normal file
159
bridge-history-api/db/orm/l2_sent_msg.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type L2SentMsg struct {
|
||||
ID uint64 `json:"id" db:"id"`
|
||||
MsgHash string `json:"msg_hash" db:"msg_hash"`
|
||||
Sender string `json:"sender" db:"sender"`
|
||||
Target string `json:"target" db:"target"`
|
||||
Value string `json:"value" db:"value"`
|
||||
Height uint64 `json:"height" db:"height"`
|
||||
Nonce uint64 `json:"nonce" db:"nonce"`
|
||||
BatchIndex uint64 `json:"batch_index" db:"batch_index"`
|
||||
MsgProof string `json:"msg_proof" db:"msg_proof"`
|
||||
MsgData string `json:"msg_data" db:"msg_data"`
|
||||
IsDeleted bool `json:"is_deleted" db:"is_deleted"`
|
||||
CreatedAt *time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at" db:"updated_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at" db:"deleted_at"`
|
||||
}
|
||||
|
||||
type l2SentMsgOrm struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// NewL2SentMsgOrm create an NewRollupBatchOrm instance
|
||||
func NewL2SentMsgOrm(db *sqlx.DB) L2SentMsgOrm {
|
||||
return &l2SentMsgOrm{db: db}
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) GetL2SentMsgByHash(msgHash string) (*L2SentMsg, error) {
|
||||
result := &L2SentMsg{}
|
||||
row := l.db.QueryRowx(`SELECT * FROM l2_sent_msg WHERE msg_hash = $1 AND NOT is_deleted;`, msgHash)
|
||||
if err := row.StructScan(result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) BatchInsertL2SentMsgDBTx(dbTx *sqlx.Tx, messages []*L2SentMsg) error {
|
||||
if len(messages) == 0 {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
messageMaps := make([]map[string]interface{}, len(messages))
|
||||
for i, msg := range messages {
|
||||
messageMaps[i] = map[string]interface{}{
|
||||
"sender": msg.Sender,
|
||||
"target": msg.Target,
|
||||
"value": msg.Value,
|
||||
"msg_hash": msg.MsgHash,
|
||||
"height": msg.Height,
|
||||
"nonce": msg.Nonce,
|
||||
"batch_index": msg.BatchIndex,
|
||||
"msg_proof": msg.MsgProof,
|
||||
"msg_data": msg.MsgData,
|
||||
}
|
||||
var exists bool
|
||||
err = dbTx.QueryRow(`SELECT EXISTS(SELECT 1 FROM l2_sent_msg WHERE (msg_hash = $1 OR nonce = $2) AND NOT is_deleted)`, msg.MsgHash, msg.Nonce).Scan(&exists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("BatchInsertL2SentMsgDBTx: l2 sent msg_hash %v already exists at height %v", msg.MsgHash, msg.Height)
|
||||
}
|
||||
}
|
||||
_, err = dbTx.NamedExec(`insert into l2_sent_msg(sender, target, value, msg_hash, height, nonce, batch_index, msg_proof, msg_data) values(:sender, :target, :value, :msg_hash, :height, :nonce, :batch_index, :msg_proof, :msg_data);`, messageMaps)
|
||||
if err != nil {
|
||||
log.Error("BatchInsertL2SentMsgDBTx: failed to insert l2 sent msgs", "msg_Hash", "err", err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) GetLatestSentMsgHeightOnL2() (int64, error) {
|
||||
row := l.db.QueryRow(`SELECT height FROM l2_sent_msg WHERE NOT is_deleted ORDER BY nonce DESC LIMIT 1;`)
|
||||
var result sql.NullInt64
|
||||
if err := row.Scan(&result); err != nil {
|
||||
if err == sql.ErrNoRows || !result.Valid {
|
||||
return -1, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
if result.Valid {
|
||||
return result.Int64, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) UpdateL2MessageProofInDBTx(ctx context.Context, dbTx *sqlx.Tx, msgHash string, proof string, batch_index uint64) error {
|
||||
if _, err := dbTx.ExecContext(ctx, l.db.Rebind("update l2_sent_msg set msg_proof = ?, batch_index = ? where msg_hash = ? AND NOT is_deleted;"), proof, batch_index, msgHash); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) GetLatestL2SentMsgBatchIndex() (int64, error) {
|
||||
row := l.db.QueryRow(`SELECT batch_index FROM l2_sent_msg WHERE msg_proof != '' AND NOT is_deleted ORDER BY batch_index DESC LIMIT 1;`)
|
||||
var result sql.NullInt64
|
||||
if err := row.Scan(&result); err != nil {
|
||||
if err == sql.ErrNoRows || !result.Valid {
|
||||
return -1, nil
|
||||
}
|
||||
return -1, err
|
||||
}
|
||||
if result.Valid {
|
||||
return result.Int64, nil
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) GetL2SentMsgMsgHashByHeightRange(startHeight, endHeight uint64) ([]*L2SentMsg, error) {
|
||||
var results []*L2SentMsg
|
||||
rows, err := l.db.Queryx(`SELECT * FROM l2_sent_msg WHERE height >= $1 AND height <= $2 AND NOT is_deleted ORDER BY nonce ASC;`, startHeight, endHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
msg := &L2SentMsg{}
|
||||
if err = rows.StructScan(msg); err != nil {
|
||||
break
|
||||
}
|
||||
results = append(results, msg)
|
||||
}
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) GetL2SentMessageByNonce(nonce uint64) (*L2SentMsg, error) {
|
||||
result := &L2SentMsg{}
|
||||
row := l.db.QueryRowx(`SELECT * FROM l2_sent_msg WHERE nonce = $1 AND NOT is_deleted;`, nonce)
|
||||
err := row.StructScan(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) GetLatestL2SentMsgLEHeight(endBlockNumber uint64) (*L2SentMsg, error) {
|
||||
result := &L2SentMsg{}
|
||||
row := l.db.QueryRowx(`select * from l2_sent_msg where height <= $1 AND NOT is_deleted order by nonce desc limit 1`, endBlockNumber)
|
||||
err := row.StructScan(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (l *l2SentMsgOrm) DeleteL2SentMsgAfterHeightDBTx(dbTx *sqlx.Tx, height int64) error {
|
||||
_, err := dbTx.Exec(`UPDATE l2_sent_msg SET is_deleted = true WHERE height > $1;`, height)
|
||||
return err
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package orm
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
@@ -31,14 +30,13 @@ func (l *relayedMsgOrm) BatchInsertRelayedMsgDBTx(dbTx *sqlx.Tx, messages []*Rel
|
||||
"layer1_hash": msg.Layer1Hash,
|
||||
"layer2_hash": msg.Layer2Hash,
|
||||
}
|
||||
|
||||
_, err = dbTx.NamedExec(`insert into relayed_msg(msg_hash, height, layer1_hash, layer2_hash) values(:msg_hash, :height, :layer1_hash, :layer2_hash);`, messageMaps[i])
|
||||
if err != nil && !strings.Contains(err.Error(), "pq: duplicate key value violates unique constraint \"relayed_msg_hash_uindex") {
|
||||
log.Error("BatchInsertRelayedMsgDBTx: failed to insert l1 cross msgs", "msg_Hashe", msg.MsgHash, "height", msg.Height, "err", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
_, err = dbTx.NamedExec(`insert into relayed_msg(msg_hash, height, layer1_hash, layer2_hash) values(:msg_hash, :height, :layer1_hash, :layer2_hash);`, messageMaps)
|
||||
if err != nil {
|
||||
log.Error("BatchInsertRelayedMsgDBTx: failed to insert l1 cross msgs", "msg_Hashe", "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *relayedMsgOrm) GetRelayedMsgByHash(msg_hash string) (*RelayedMsg, error) {
|
||||
|
||||
@@ -16,6 +16,9 @@ type OrmFactory interface {
|
||||
orm.L1CrossMsgOrm
|
||||
orm.L2CrossMsgOrm
|
||||
orm.RelayedMsgOrm
|
||||
orm.L2SentMsgOrm
|
||||
orm.RollupBatchOrm
|
||||
GetTotalCrossMsgCountByAddress(sender string) (uint64, error)
|
||||
GetCrossMsgsByAddressWithOffset(sender string, offset int64, limit int64) ([]*orm.CrossMsg, error)
|
||||
GetDB() *sqlx.DB
|
||||
Beginx() (*sqlx.Tx, error)
|
||||
@@ -26,6 +29,8 @@ type ormFactory struct {
|
||||
orm.L1CrossMsgOrm
|
||||
orm.L2CrossMsgOrm
|
||||
orm.RelayedMsgOrm
|
||||
orm.L2SentMsgOrm
|
||||
orm.RollupBatchOrm
|
||||
*sqlx.DB
|
||||
}
|
||||
|
||||
@@ -44,10 +49,12 @@ func NewOrmFactory(cfg *config.Config) (OrmFactory, error) {
|
||||
}
|
||||
|
||||
return &ormFactory{
|
||||
L1CrossMsgOrm: orm.NewL1CrossMsgOrm(db),
|
||||
L2CrossMsgOrm: orm.NewL2CrossMsgOrm(db),
|
||||
RelayedMsgOrm: orm.NewRelayedMsgOrm(db),
|
||||
DB: db,
|
||||
L1CrossMsgOrm: orm.NewL1CrossMsgOrm(db),
|
||||
L2CrossMsgOrm: orm.NewL2CrossMsgOrm(db),
|
||||
RelayedMsgOrm: orm.NewRelayedMsgOrm(db),
|
||||
L2SentMsgOrm: orm.NewL2SentMsgOrm(db),
|
||||
RollupBatchOrm: orm.NewRollupBatchOrm(db),
|
||||
DB: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -59,10 +66,19 @@ func (o *ormFactory) Beginx() (*sqlx.Tx, error) {
|
||||
return o.DB.Beginx()
|
||||
}
|
||||
|
||||
func (o *ormFactory) GetTotalCrossMsgCountByAddress(sender string) (uint64, error) {
|
||||
var count uint64
|
||||
row := o.DB.QueryRowx(`SELECT COUNT(*) FROM cross_message WHERE sender = $1 AND NOT is_deleted;`, sender)
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (o *ormFactory) GetCrossMsgsByAddressWithOffset(sender string, offset int64, limit int64) ([]*orm.CrossMsg, error) {
|
||||
para := sender
|
||||
var results []*orm.CrossMsg
|
||||
rows, err := o.DB.Queryx(`SELECT * FROM cross_message WHERE sender = $1 AND NOT is_deleted ORDER BY id DESC LIMIT $2 OFFSET $3;`, para, limit, offset)
|
||||
rows, err := o.DB.Queryx(`SELECT * FROM cross_message WHERE sender = $1 AND NOT is_deleted ORDER BY block_timestamp DESC NULLS FIRST, id DESC LIMIT $2 OFFSET $3;`, para, limit, offset)
|
||||
if err != nil || rows == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
module bridge-history-api
|
||||
|
||||
go 1.20
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/ethereum/go-ethereum v1.11.6
|
||||
github.com/ethereum/go-ethereum v1.12.0
|
||||
github.com/iris-contrib/middleware/cors v0.0.0-20230531125531-980d3a09a458
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/kataras/iris/v12 v12.2.0
|
||||
github.com/lib/pq v1.10.7
|
||||
@@ -26,6 +27,7 @@ require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.7.0 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/btcsuite/btcd v0.20.1-beta // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||
@@ -34,14 +36,17 @@ require (
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect
|
||||
github.com/cockroachdb/redact v1.1.3 // indirect
|
||||
github.com/consensys/bavard v0.1.13 // indirect
|
||||
github.com/consensys/gnark-crypto v0.10.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/crate-crypto/go-kzg-4844 v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
||||
github.com/docker/docker v20.10.21+incompatible // indirect
|
||||
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v0.2.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect
|
||||
github.com/flosch/pongo2/v4 v4.0.2 // indirect
|
||||
@@ -92,6 +97,7 @@ require (
|
||||
github.com/microcosm-cc/bluemonday v1.0.23 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||
github.com/mmcloughlin/addchain v0.4.0 // indirect
|
||||
github.com/nats-io/nats.go v1.23.0 // indirect
|
||||
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
@@ -115,6 +121,7 @@ require (
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/status-im/keycard-go v0.2.0 // indirect
|
||||
github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.12.4 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.4 // indirect
|
||||
@@ -127,12 +134,12 @@ require (
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/yosssi/ace v0.0.5 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||
@@ -142,5 +149,6 @@ require (
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
rsc.io/tmplfunc v0.0.3 // indirect
|
||||
|
||||
)
|
||||
|
||||
@@ -32,6 +32,8 @@ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd3
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo=
|
||||
github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
@@ -65,12 +67,18 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lg
|
||||
github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
|
||||
github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
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.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA=
|
||||
github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crate-crypto/go-kzg-4844 v0.2.0 h1:UVuHOE+5tIWrim4zf/Xaa43+MIsDCPyW76QhUpiMGj4=
|
||||
github.com/crate-crypto/go-kzg-4844 v0.2.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -92,8 +100,6 @@ github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+
|
||||
github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog=
|
||||
github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -101,8 +107,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/ethereum/go-ethereum v1.11.6 h1:2VF8Mf7XiSUfmoNOy3D+ocfl9Qu8baQBrCNbo2CXQ8E=
|
||||
github.com/ethereum/go-ethereum v1.11.6/go.mod h1:+a8pUj1tOyJ2RinsNQD4326YS+leSoKGiG/uVVb0x6Y=
|
||||
github.com/ethereum/c-kzg-4844 v0.2.0 h1:+cUvymlnoDDQgMInp25Bo3OmLajmmY8mLJ/tLjqd77Q=
|
||||
github.com/ethereum/c-kzg-4844 v0.2.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8=
|
||||
github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0=
|
||||
github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
@@ -193,6 +201,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -233,6 +242,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3
|
||||
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
|
||||
github.com/iris-contrib/httpexpect/v2 v2.12.1 h1:3cTZSyBBen/kfjCtgNFoUKi1u0FVXNaAjyRJOo6AVS4=
|
||||
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
|
||||
github.com/iris-contrib/middleware/cors v0.0.0-20230531125531-980d3a09a458 h1:V60rHQJc6DieKV1BqHIGclraPdO4kinuFAZIrPGHN7s=
|
||||
github.com/iris-contrib/middleware/cors v0.0.0-20230531125531-980d3a09a458/go.mod h1:7eVziAp1yUwFB/ZMg71n84VWQH+7wukvxcHuF2e7cbg=
|
||||
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
|
||||
@@ -290,6 +301,7 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
|
||||
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
@@ -341,6 +353,9 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
|
||||
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
||||
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
||||
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -453,6 +468,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnygnB34h8OKbnNJFVUtWSKIKb1KukdV8bILUM=
|
||||
github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b/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/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE=
|
||||
@@ -520,8 +537,8 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
@@ -611,8 +628,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -621,8 +638,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
@@ -720,3 +737,5 @@ modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=
|
||||
modernc.org/strutil v1.1.2 h1:iFBDH6j1Z0bN/Q9udJnnFoFpENA4252qe/7/5woE5MI=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
|
||||
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
|
||||
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
|
||||
|
||||
@@ -4,7 +4,7 @@ import "bridge-history-api/service"
|
||||
|
||||
type Data struct {
|
||||
Result []*service.TxHistoryInfo `json:"result"`
|
||||
Total int `json:"total"`
|
||||
Total uint64 `json:"total"`
|
||||
}
|
||||
|
||||
type QueryByAddressResponse struct {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -19,20 +20,32 @@ type Finalized struct {
|
||||
BlockTimestamp *time.Time `json:"blockTimestamp"` // uselesss
|
||||
}
|
||||
|
||||
type UserClaimInfo struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Value string `json:"value"`
|
||||
Nonce string `json:"nonce"`
|
||||
BatchHash string `json:"batch_hash"`
|
||||
Message string `json:"message"`
|
||||
Proof string `json:"proof"`
|
||||
BatchIndex string `json:"batch_index"`
|
||||
}
|
||||
|
||||
type TxHistoryInfo struct {
|
||||
Hash string `json:"hash"`
|
||||
Amount string `json:"amount"`
|
||||
To string `json:"to"` // useless
|
||||
IsL1 bool `json:"isL1"`
|
||||
BlockNumber uint64 `json:"blockNumber"`
|
||||
BlockTimestamp *time.Time `json:"blockTimestamp"` // useless
|
||||
FinalizeTx *Finalized `json:"finalizeTx"`
|
||||
CreatedAt *time.Time `json:"createdTime"`
|
||||
Hash string `json:"hash"`
|
||||
Amount string `json:"amount"`
|
||||
To string `json:"to"` // useless
|
||||
IsL1 bool `json:"isL1"`
|
||||
BlockNumber uint64 `json:"blockNumber"`
|
||||
BlockTimestamp *time.Time `json:"blockTimestamp"` // useless
|
||||
FinalizeTx *Finalized `json:"finalizeTx"`
|
||||
ClaimInfo *UserClaimInfo `json:"claimInfo"`
|
||||
CreatedAt *time.Time `json:"createdTime"`
|
||||
}
|
||||
|
||||
// HistoryService example service.
|
||||
type HistoryService interface {
|
||||
GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, error)
|
||||
GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, uint64, error)
|
||||
GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, error)
|
||||
}
|
||||
|
||||
@@ -47,6 +60,30 @@ type historyBackend struct {
|
||||
db db.OrmFactory
|
||||
}
|
||||
|
||||
func GetCrossTxClaimInfo(msgHash string, db db.OrmFactory) *UserClaimInfo {
|
||||
l2sentMsg, err := db.GetL2SentMsgByHash(msgHash)
|
||||
if err != nil {
|
||||
log.Debug("GetCrossTxClaimInfo failed", "error", err)
|
||||
return &UserClaimInfo{}
|
||||
}
|
||||
batch, err := db.GetRollupBatchByIndex(l2sentMsg.BatchIndex)
|
||||
if err != nil {
|
||||
log.Debug("GetCrossTxClaimInfo failed", "error", err)
|
||||
return &UserClaimInfo{}
|
||||
}
|
||||
return &UserClaimInfo{
|
||||
From: l2sentMsg.Sender,
|
||||
To: l2sentMsg.Target,
|
||||
Value: l2sentMsg.Value,
|
||||
Nonce: strconv.FormatUint(l2sentMsg.Nonce, 10),
|
||||
Message: l2sentMsg.MsgData,
|
||||
Proof: l2sentMsg.MsgProof,
|
||||
BatchHash: batch.BatchHash,
|
||||
BatchIndex: strconv.FormatUint(l2sentMsg.BatchIndex, 10),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func updateCrossTxHash(msgHash string, txInfo *TxHistoryInfo, db db.OrmFactory) {
|
||||
relayed, err := db.GetRelayedMsgByHash(msgHash)
|
||||
if err != nil {
|
||||
@@ -69,28 +106,35 @@ func updateCrossTxHash(msgHash string, txInfo *TxHistoryInfo, db db.OrmFactory)
|
||||
|
||||
}
|
||||
|
||||
func (h *historyBackend) GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, error) {
|
||||
txHistories := make([]*TxHistoryInfo, 0)
|
||||
func (h *historyBackend) GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, uint64, error) {
|
||||
var txHistories []*TxHistoryInfo
|
||||
total, err := h.db.GetTotalCrossMsgCountByAddress(address.String())
|
||||
if err != nil || total == 0 {
|
||||
return txHistories, 0, err
|
||||
}
|
||||
result, err := h.db.GetCrossMsgsByAddressWithOffset(address.String(), offset, limit)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, msg := range result {
|
||||
txHistory := &TxHistoryInfo{
|
||||
Hash: msg.MsgHash,
|
||||
Amount: msg.Amount,
|
||||
To: msg.Target,
|
||||
IsL1: msg.MsgType == int(orm.Layer1Msg),
|
||||
BlockNumber: msg.Height,
|
||||
CreatedAt: msg.CreatedAt,
|
||||
Hash: msg.Layer1Hash + msg.Layer2Hash,
|
||||
Amount: msg.Amount,
|
||||
To: msg.Target,
|
||||
IsL1: msg.MsgType == int(orm.Layer1Msg),
|
||||
BlockNumber: msg.Height,
|
||||
BlockTimestamp: msg.Timestamp,
|
||||
CreatedAt: msg.CreatedAt,
|
||||
FinalizeTx: &Finalized{
|
||||
Hash: "",
|
||||
},
|
||||
ClaimInfo: GetCrossTxClaimInfo(msg.MsgHash, h.db),
|
||||
}
|
||||
updateCrossTxHash(msg.MsgHash, txHistory, h.db)
|
||||
txHistories = append(txHistories, txHistory)
|
||||
}
|
||||
return txHistories, nil
|
||||
return txHistories, total, nil
|
||||
}
|
||||
|
||||
func (h *historyBackend) GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, error) {
|
||||
@@ -102,12 +146,13 @@ func (h *historyBackend) GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, erro
|
||||
}
|
||||
if l1result != nil {
|
||||
txHistory := &TxHistoryInfo{
|
||||
Hash: l1result.Layer1Hash,
|
||||
Amount: l1result.Amount,
|
||||
To: l1result.Target,
|
||||
IsL1: true,
|
||||
BlockNumber: l1result.Height,
|
||||
CreatedAt: l1result.CreatedAt,
|
||||
Hash: l1result.Layer1Hash,
|
||||
Amount: l1result.Amount,
|
||||
To: l1result.Target,
|
||||
IsL1: true,
|
||||
BlockNumber: l1result.Height,
|
||||
BlockTimestamp: l1result.Timestamp,
|
||||
CreatedAt: l1result.CreatedAt,
|
||||
FinalizeTx: &Finalized{
|
||||
Hash: "",
|
||||
},
|
||||
@@ -122,15 +167,17 @@ func (h *historyBackend) GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, erro
|
||||
}
|
||||
if l2result != nil {
|
||||
txHistory := &TxHistoryInfo{
|
||||
Hash: l2result.Layer2Hash,
|
||||
Amount: l2result.Amount,
|
||||
To: l2result.Target,
|
||||
IsL1: false,
|
||||
BlockNumber: l2result.Height,
|
||||
CreatedAt: l2result.CreatedAt,
|
||||
Hash: l2result.Layer2Hash,
|
||||
Amount: l2result.Amount,
|
||||
To: l2result.Target,
|
||||
IsL1: false,
|
||||
BlockNumber: l2result.Height,
|
||||
BlockTimestamp: l2result.Timestamp,
|
||||
CreatedAt: l2result.CreatedAt,
|
||||
FinalizeTx: &Finalized{
|
||||
Hash: "",
|
||||
},
|
||||
ClaimInfo: GetCrossTxClaimInfo(l2result.MsgHash, h.db),
|
||||
}
|
||||
updateCrossTxHash(l2result.MsgHash, txHistory, h.db)
|
||||
txHistories = append(txHistories, txHistory)
|
||||
|
||||
File diff suppressed because one or more lines are too long
305
bridge-history-api/utils/parse_event.go
Normal file
305
bridge-history-api/utils/parse_event.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
backendabi "bridge-history-api/abi"
|
||||
"bridge-history-api/db/orm"
|
||||
)
|
||||
|
||||
type MsgHashWrapper struct {
|
||||
MsgHash common.Hash
|
||||
TxHash common.Hash
|
||||
}
|
||||
|
||||
type CachedParsedTxCalldata struct {
|
||||
CallDataIndex uint64
|
||||
BatchIndices []uint64
|
||||
StartBlocks []uint64
|
||||
EndBlocks []uint64
|
||||
}
|
||||
|
||||
func ParseBackendL1EventLogs(logs []types.Log) ([]*orm.CrossMsg, []MsgHashWrapper, []*orm.RelayedMsg, error) {
|
||||
// Need use contract abi to parse event Log
|
||||
// Can only be tested after we have our contracts set up
|
||||
|
||||
var l1CrossMsg []*orm.CrossMsg
|
||||
var relayedMsgs []*orm.RelayedMsg
|
||||
var msgHashes []MsgHashWrapper
|
||||
for _, vlog := range logs {
|
||||
switch vlog.Topics[0] {
|
||||
case backendabi.L1DepositETHSig:
|
||||
event := backendabi.DepositETH{}
|
||||
err := UnpackLog(backendabi.L1ETHGatewayABI, &event, "DepositETH", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositETH event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ETH),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
case backendabi.L1DepositERC20Sig:
|
||||
event := backendabi.ERC20MessageEvent{}
|
||||
err := UnpackLog(backendabi.L1StandardERC20GatewayABI, &event, "DepositERC20", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositERC20 event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ERC20),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
})
|
||||
case backendabi.L1DepositERC721Sig:
|
||||
event := backendabi.ERC721MessageEvent{}
|
||||
err := UnpackLog(backendabi.L1ERC721GatewayABI, &event, "DepositERC721", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositERC721 event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC721),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
})
|
||||
case backendabi.L1DepositERC1155Sig:
|
||||
event := backendabi.ERC1155MessageEvent{}
|
||||
err := UnpackLog(backendabi.L1ERC1155GatewayABI, &event, "DepositERC1155", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack DepositERC1155 event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
l1CrossMsg = append(l1CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC1155),
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
Amount: event.Amount.String(),
|
||||
})
|
||||
case backendabi.L1SentMessageEventSignature:
|
||||
event := backendabi.L1SentMessageEvent{}
|
||||
err := UnpackLog(backendabi.L1ScrollMessengerABI, &event, "SentMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack SentMessage event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
msgHash := ComputeMessageHash(event.Sender, event.Target, event.Value, event.MessageNonce, event.Message)
|
||||
msgHashes = append(msgHashes, MsgHashWrapper{
|
||||
MsgHash: msgHash,
|
||||
TxHash: vlog.TxHash})
|
||||
case backendabi.L1RelayedMessageEventSignature:
|
||||
event := backendabi.L1RelayedMessageEvent{}
|
||||
err := UnpackLog(backendabi.L1ScrollMessengerABI, &event, "RelayedMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack RelayedMessage event", "err", err)
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, err
|
||||
}
|
||||
relayedMsgs = append(relayedMsgs, &orm.RelayedMsg{
|
||||
MsgHash: event.MessageHash.String(),
|
||||
Height: vlog.BlockNumber,
|
||||
Layer1Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return l1CrossMsg, msgHashes, relayedMsgs, nil
|
||||
}
|
||||
|
||||
func ParseBackendL2EventLogs(logs []types.Log) ([]*orm.CrossMsg, []MsgHashWrapper, []*orm.RelayedMsg, []*orm.L2SentMsg, error) {
|
||||
// Need use contract abi to parse event Log
|
||||
// Can only be tested after we have our contracts set up
|
||||
|
||||
var l2CrossMsg []*orm.CrossMsg
|
||||
// this is use to confirm finalized l1 msg
|
||||
var relayedMsgs []*orm.RelayedMsg
|
||||
var l2SentMsg []*orm.L2SentMsg
|
||||
var msgHashes []MsgHashWrapper
|
||||
for _, vlog := range logs {
|
||||
switch vlog.Topics[0] {
|
||||
case backendabi.L2WithdrawETHSig:
|
||||
event := backendabi.DepositETH{}
|
||||
err := UnpackLog(backendabi.L2ETHGatewayABI, &event, "WithdrawETH", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawETH event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, l2SentMsg, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ETH),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
case backendabi.L2WithdrawERC20Sig:
|
||||
event := backendabi.ERC20MessageEvent{}
|
||||
err := UnpackLog(backendabi.L2StandardERC20GatewayABI, &event, "WithdrawERC20", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawERC20 event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, l2SentMsg, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Amount: event.Amount.String(),
|
||||
Asset: int(orm.ERC20),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
})
|
||||
case backendabi.L2WithdrawERC721Sig:
|
||||
event := backendabi.ERC721MessageEvent{}
|
||||
err := UnpackLog(backendabi.L2ERC721GatewayABI, &event, "WithdrawERC721", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawERC721 event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, l2SentMsg, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC721),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
})
|
||||
case backendabi.L2WithdrawERC1155Sig:
|
||||
event := backendabi.ERC1155MessageEvent{}
|
||||
err := UnpackLog(backendabi.L2ERC1155GatewayABI, &event, "WithdrawERC1155", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack WithdrawERC1155 event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, l2SentMsg, err
|
||||
}
|
||||
l2CrossMsg = append(l2CrossMsg, &orm.CrossMsg{
|
||||
Height: vlog.BlockNumber,
|
||||
Sender: event.From.String(),
|
||||
Target: event.To.String(),
|
||||
Asset: int(orm.ERC1155),
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
Layer1Token: event.L1Token.Hex(),
|
||||
Layer2Token: event.L2Token.Hex(),
|
||||
TokenID: event.TokenID.Uint64(),
|
||||
Amount: event.Amount.String(),
|
||||
})
|
||||
case backendabi.L2SentMessageEventSignature:
|
||||
event := backendabi.L2SentMessageEvent{}
|
||||
err := UnpackLog(backendabi.L2ScrollMessengerABI, &event, "SentMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack SentMessage event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, l2SentMsg, err
|
||||
}
|
||||
msgHash := ComputeMessageHash(event.Sender, event.Target, event.Value, event.MessageNonce, event.Message)
|
||||
msgHashes = append(msgHashes, MsgHashWrapper{
|
||||
MsgHash: msgHash,
|
||||
TxHash: vlog.TxHash})
|
||||
l2SentMsg = append(l2SentMsg, &orm.L2SentMsg{
|
||||
Sender: event.Sender.Hex(),
|
||||
Target: event.Target.Hex(),
|
||||
Value: event.Value.String(),
|
||||
MsgHash: msgHash.Hex(),
|
||||
Height: vlog.BlockNumber,
|
||||
Nonce: event.MessageNonce.Uint64(),
|
||||
MsgData: hexutil.Encode(event.Message),
|
||||
})
|
||||
case backendabi.L2RelayedMessageEventSignature:
|
||||
event := backendabi.L2RelayedMessageEvent{}
|
||||
err := UnpackLog(backendabi.L2ScrollMessengerABI, &event, "RelayedMessage", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack RelayedMessage event", "err", err)
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, l2SentMsg, err
|
||||
}
|
||||
relayedMsgs = append(relayedMsgs, &orm.RelayedMsg{
|
||||
MsgHash: event.MessageHash.String(),
|
||||
Height: vlog.BlockNumber,
|
||||
Layer2Hash: vlog.TxHash.Hex(),
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
return l2CrossMsg, msgHashes, relayedMsgs, l2SentMsg, nil
|
||||
}
|
||||
|
||||
func ParseBatchInfoFromScrollChain(ctx context.Context, client *ethclient.Client, logs []types.Log) ([]*orm.RollupBatch, error) {
|
||||
var rollupBatches []*orm.RollupBatch
|
||||
cache := make(map[string]CachedParsedTxCalldata)
|
||||
for _, vlog := range logs {
|
||||
switch vlog.Topics[0] {
|
||||
case backendabi.L1CommitBatchEventSignature:
|
||||
event := backendabi.L1CommitBatchEvent{}
|
||||
err := UnpackLog(backendabi.ScrollChainABI, &event, "CommitBatch", vlog)
|
||||
if err != nil {
|
||||
log.Warn("Failed to unpack CommitBatch event", "err", err)
|
||||
return rollupBatches, err
|
||||
}
|
||||
if _, ok := cache[vlog.TxHash.Hex()]; ok {
|
||||
c := cache[vlog.TxHash.Hex()]
|
||||
c.CallDataIndex++
|
||||
rollupBatches = append(rollupBatches, &orm.RollupBatch{
|
||||
CommitHeight: vlog.BlockNumber,
|
||||
BatchIndex: c.BatchIndices[c.CallDataIndex],
|
||||
BatchHash: event.BatchHash.Hex(),
|
||||
StartBlockNumber: c.StartBlocks[c.CallDataIndex],
|
||||
EndBlockNumber: c.EndBlocks[c.CallDataIndex],
|
||||
})
|
||||
cache[vlog.TxHash.Hex()] = c
|
||||
continue
|
||||
}
|
||||
|
||||
commitTx, isPending, err := client.TransactionByHash(ctx, vlog.TxHash)
|
||||
if err != nil || isPending {
|
||||
log.Warn("Failed to get commit Batch tx receipt or the tx is still pending", "err", err)
|
||||
return rollupBatches, err
|
||||
}
|
||||
indices, startBlocks, endBlocks, err := GetBatchRangeFromCalldataV1(commitTx.Data())
|
||||
if err != nil {
|
||||
log.Warn("Failed to get batch range from calldata", "hash", commitTx.Hash().Hex(), "height", vlog.BlockNumber)
|
||||
return rollupBatches, err
|
||||
}
|
||||
cache[vlog.TxHash.Hex()] = CachedParsedTxCalldata{
|
||||
CallDataIndex: 0,
|
||||
BatchIndices: indices,
|
||||
StartBlocks: startBlocks,
|
||||
EndBlocks: endBlocks,
|
||||
}
|
||||
rollupBatches = append(rollupBatches, &orm.RollupBatch{
|
||||
CommitHeight: vlog.BlockNumber,
|
||||
BatchIndex: indices[0],
|
||||
BatchHash: event.BatchHash.Hex(),
|
||||
StartBlockNumber: startBlocks[0],
|
||||
EndBlockNumber: endBlocks[0],
|
||||
})
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return rollupBatches, nil
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
@@ -14,6 +17,11 @@ import (
|
||||
backendabi "bridge-history-api/abi"
|
||||
)
|
||||
|
||||
// Keccak2 compute the keccack256 of two concatenations of bytes32
|
||||
func Keccak2(a common.Hash, b common.Hash) common.Hash {
|
||||
return common.BytesToHash(crypto.Keccak256(append(a.Bytes()[:], b.Bytes()[:]...)))
|
||||
}
|
||||
|
||||
func GetSafeBlockNumber(ctx context.Context, client *ethclient.Client, confirmations uint64) (uint64, error) {
|
||||
number, err := client.BlockNumber(ctx)
|
||||
if err != nil || number <= confirmations {
|
||||
@@ -54,3 +62,95 @@ func ComputeMessageHash(
|
||||
data, _ := backendabi.L2ScrollMessengerABI.Pack("relayMessage", sender, target, value, messageNonce, message)
|
||||
return common.BytesToHash(crypto.Keccak256(data))
|
||||
}
|
||||
|
||||
type commitBatchArgs struct {
|
||||
Version uint8
|
||||
ParentBatchHeader []byte
|
||||
Chunks [][]byte
|
||||
SkippedL1MessageBitmap []byte
|
||||
}
|
||||
|
||||
// GetBatchRangeFromCalldataV2 find the block range from calldata, both inclusive.
|
||||
func GetBatchRangeFromCalldataV2(calldata []byte) (uint64, uint64, uint64, error) {
|
||||
method := backendabi.ScrollChainV2ABI.Methods["commitBatch"]
|
||||
values, err := method.Inputs.Unpack(calldata[4:])
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
args := commitBatchArgs{}
|
||||
err = method.Inputs.Copy(&args, values)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
var startBlock uint64
|
||||
var finishBlock uint64
|
||||
|
||||
// decode batchIndex from ParentBatchHeader
|
||||
if len(args.ParentBatchHeader) < 9 {
|
||||
return 0, 0, 0, errors.New("invalid parent batch header")
|
||||
}
|
||||
batchIndex := binary.BigEndian.Uint64(args.ParentBatchHeader[1:9]) + 1
|
||||
|
||||
// decode blocks from chunk and assume that there's no empty chunk
|
||||
// | 1 byte | 60 bytes | ... | 60 bytes |
|
||||
// | num blocks | block 1 | ... | block n |
|
||||
if len(args.Chunks) == 0 {
|
||||
return 0, 0, 0, errors.New("invalid chunks")
|
||||
}
|
||||
chunk := args.Chunks[0]
|
||||
block := chunk[1:61] // first block in chunk
|
||||
startBlock = binary.BigEndian.Uint64(block[0:8])
|
||||
|
||||
chunk = args.Chunks[len(args.Chunks)-1]
|
||||
lastBlockIndex := int(chunk[0]) - 1
|
||||
block = chunk[1+lastBlockIndex*60 : 1+lastBlockIndex*60+60] // last block in chunk
|
||||
finishBlock = binary.BigEndian.Uint64(block[0:8])
|
||||
|
||||
return batchIndex, startBlock, finishBlock, err
|
||||
}
|
||||
|
||||
// GetBatchRangeFromCalldataV1 find the block range from calldata, both inclusive.
|
||||
func GetBatchRangeFromCalldataV1(calldata []byte) ([]uint64, []uint64, []uint64, error) {
|
||||
var batchIndices []uint64
|
||||
var startBlocks []uint64
|
||||
var finishBlocks []uint64
|
||||
if bytes.Equal(calldata[0:4], common.Hex2Bytes("cb905499")) {
|
||||
// commitBatches
|
||||
method := backendabi.ScrollChainABI.Methods["commitBatches"]
|
||||
values, err := method.Inputs.Unpack(calldata[4:])
|
||||
if err != nil {
|
||||
return batchIndices, startBlocks, finishBlocks, err
|
||||
}
|
||||
args := make([]backendabi.IScrollChainBatch, len(values))
|
||||
err = method.Inputs.Copy(&args, values)
|
||||
if err != nil {
|
||||
return batchIndices, startBlocks, finishBlocks, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
batchIndices = append(batchIndices, args[i].BatchIndex)
|
||||
startBlocks = append(startBlocks, args[i].Blocks[0].BlockNumber)
|
||||
finishBlocks = append(finishBlocks, args[i].Blocks[len(args[i].Blocks)-1].BlockNumber)
|
||||
}
|
||||
} else if bytes.Equal(calldata[0:4], common.Hex2Bytes("8c73235d")) {
|
||||
// commitBatch
|
||||
method := backendabi.ScrollChainABI.Methods["commitBatch"]
|
||||
values, err := method.Inputs.Unpack(calldata[4:])
|
||||
if err != nil {
|
||||
return batchIndices, startBlocks, finishBlocks, err
|
||||
}
|
||||
|
||||
args := backendabi.IScrollChainBatch{}
|
||||
err = method.Inputs.Copy(&args, values)
|
||||
if err != nil {
|
||||
return batchIndices, startBlocks, finishBlocks, err
|
||||
}
|
||||
batchIndices = append(batchIndices, args.BatchIndex)
|
||||
startBlocks = append(startBlocks, args.Blocks[0].BlockNumber)
|
||||
finishBlocks = append(finishBlocks, args.Blocks[len(args.Blocks)-1].BlockNumber)
|
||||
} else {
|
||||
return batchIndices, startBlocks, finishBlocks, errors.New("invalid selector")
|
||||
}
|
||||
return batchIndices, startBlocks, finishBlocks, nil
|
||||
}
|
||||
|
||||
64
bridge-history-api/utils/utils_test.go
Normal file
64
bridge-history-api/utils/utils_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"bridge-history-api/utils"
|
||||
)
|
||||
|
||||
func TestKeccak2(t *testing.T) {
|
||||
a := common.HexToHash("0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0")
|
||||
b := common.HexToHash("0x222ff5e0b5877792c2bc1670e2ccd0c2c97cd7bb1672a57d598db05092d3d72c")
|
||||
c := utils.Keccak2(a, b)
|
||||
assert.NotEmpty(t, c)
|
||||
assert.NotEqual(t, a, c)
|
||||
assert.NotEqual(t, b, c)
|
||||
assert.Equal(t, "0xc0ffbd7f501bd3d49721b0724b2bff657cb2378f15d5a9b97cd7ea5bf630d512", c.Hex())
|
||||
}
|
||||
|
||||
func TestGetBatchRangeFromCalldataV2(t *testing.T) {
|
||||
// single chunk
|
||||
batchIndex, start, finish, err := utils.GetBatchRangeFromCalldataV2(common.Hex2Bytes("1325aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000005900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003d0100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000100000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, start, uint64(1))
|
||||
assert.Equal(t, finish, uint64(1))
|
||||
assert.Equal(t, batchIndex, uint64(1))
|
||||
|
||||
// multiple chunk
|
||||
batchIndex, start, finish, err = utils.GetBatchRangeFromCalldataV2(common.Hex2Bytes("1325aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000007900000000000000000100000000000000010000000000000001038433daac85a0b03cd443ed50bc85e832c883061651ae2182b2984751e0b340119b828c2a2798d2c957228ebeaff7e10bb099ae0d4e224f3eeb779ff61cba610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004c01000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000010000000001000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b403000000000000000b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000300000000000000000b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00050000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c01000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000100000000010000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa800000000000000000000000000000000000000000000000000000000000000aa"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, start, uint64(10))
|
||||
assert.Equal(t, finish, uint64(20))
|
||||
assert.Equal(t, batchIndex, uint64(2))
|
||||
}
|
||||
|
||||
func TestGetBatchRangeFromCalldataV1(t *testing.T) {
|
||||
calldata, err := os.ReadFile("../testdata/commit-batches-0x3095e91db7ba4a6fbf4654d607db322e58ff5579c502219c8024acaea74cf311.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// multiple batches
|
||||
batchIndices, startBlocks, finishBlocks, err := utils.GetBatchRangeFromCalldataV1(common.Hex2Bytes(string(calldata[:])))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(batchIndices), 5)
|
||||
assert.Equal(t, len(startBlocks), 5)
|
||||
assert.Equal(t, len(finishBlocks), 5)
|
||||
assert.Equal(t, batchIndices[0], uint64(1))
|
||||
assert.Equal(t, batchIndices[1], uint64(2))
|
||||
assert.Equal(t, batchIndices[2], uint64(3))
|
||||
assert.Equal(t, batchIndices[3], uint64(4))
|
||||
assert.Equal(t, batchIndices[4], uint64(5))
|
||||
assert.Equal(t, startBlocks[0], uint64(1))
|
||||
assert.Equal(t, startBlocks[1], uint64(6))
|
||||
assert.Equal(t, startBlocks[2], uint64(7))
|
||||
assert.Equal(t, startBlocks[3], uint64(19))
|
||||
assert.Equal(t, startBlocks[4], uint64(20))
|
||||
assert.Equal(t, finishBlocks[0], uint64(5))
|
||||
assert.Equal(t, finishBlocks[1], uint64(6))
|
||||
assert.Equal(t, finishBlocks[2], uint64(18))
|
||||
assert.Equal(t, finishBlocks[3], uint64(19))
|
||||
assert.Equal(t, finishBlocks[4], uint64(20))
|
||||
}
|
||||
@@ -5,8 +5,8 @@ IMAGE_VERSION=latest
|
||||
REPO_ROOT_DIR=./..
|
||||
|
||||
mock_abi:
|
||||
go run github.com/scroll-tech/go-ethereum/cmd/abigen --sol mock_bridge/MockBridgeL1.sol --pkg mock_bridge --out mock_bridge/MockBridgeL1.go
|
||||
go run github.com/scroll-tech/go-ethereum/cmd/abigen --sol mock_bridge/MockBridgeL2.sol --pkg mock_bridge --out mock_bridge/MockBridgeL2.go
|
||||
cd .. && go run github.com/scroll-tech/go-ethereum/cmd/abigen --sol ./bridge/mock_bridge/MockBridgeL1.sol --pkg mock_bridge --out ./bridge/mock_bridge/MockBridgeL1.go
|
||||
cd .. && go run github.com/scroll-tech/go-ethereum/cmd/abigen --sol ./bridge/mock_bridge/MockBridgeL2.sol --pkg mock_bridge --out ./bridge/mock_bridge/MockBridgeL2.go
|
||||
|
||||
bridge_bins: ## Builds the Bridge bins.
|
||||
go build -o $(PWD)/build/bin/event_watcher ./cmd/event_watcher/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,9 +16,9 @@ func TestEventSignature(t *testing.T) {
|
||||
assert.Equal(L1FailedRelayedMessageEventSignature, common.HexToHash("99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f"))
|
||||
|
||||
assert.Equal(L1CommitBatchEventSignature, common.HexToHash("2cdc615c74452778c0fb6184735e014c13aad2b62774fe0b09bd1dcc2cc14a62"))
|
||||
assert.Equal(L1FinalizeBatchEventSignature, common.HexToHash("6be443154c959a7a1645b4392b6fa97d8e8ab6e8fd853d7085e8867083737d79"))
|
||||
assert.Equal(L1FinalizeBatchEventSignature, common.HexToHash("9d3058a3cb9739a2527f22dd9a4138065844037d3004254952e2458d808cc364"))
|
||||
|
||||
assert.Equal(L1QueueTransactionEventSignature, common.HexToHash("bdcc7517f8fe3db6506dfd910942d0bbecaf3d6a506dadea65b0d988e75b9439"))
|
||||
assert.Equal(L1QueueTransactionEventSignature, common.HexToHash("69cfcb8e6d4192b8aba9902243912587f37e550d75c1fa801491fce26717f37e"))
|
||||
|
||||
assert.Equal(L2SentMessageEventSignature, common.HexToHash("104371f3b442861a2a7b82a070afbbaab748bb13757bf47769e170e37809ec1e"))
|
||||
assert.Equal(L2RelayedMessageEventSignature, common.HexToHash("4641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133c"))
|
||||
@@ -35,10 +35,10 @@ func TestPackRelayL2MessageWithProof(t *testing.T) {
|
||||
assert.NoError(err)
|
||||
|
||||
proof := IL1ScrollMessengerL2MessageProof{
|
||||
BatchHash: common.Hash{},
|
||||
MerkleProof: make([]byte, 0),
|
||||
BatchIndex: big.NewInt(0),
|
||||
MerkleProof: []byte{},
|
||||
}
|
||||
_, err = l1MessengerABI.Pack("relayMessageWithProof", common.Address{}, common.Address{}, big.NewInt(0), big.NewInt(0), make([]byte, 0), proof)
|
||||
_, err = l1MessengerABI.Pack("relayMessageWithProof", common.Address{}, common.Address{}, big.NewInt(0), big.NewInt(0), []byte{}, proof)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
@@ -48,27 +48,12 @@ func TestPackCommitBatch(t *testing.T) {
|
||||
scrollChainABI, err := ScrollChainMetaData.GetAbi()
|
||||
assert.NoError(err)
|
||||
|
||||
header := IScrollChainBlockContext{
|
||||
BlockHash: common.Hash{},
|
||||
ParentHash: common.Hash{},
|
||||
BlockNumber: 0,
|
||||
Timestamp: 0,
|
||||
BaseFee: big.NewInt(0),
|
||||
GasLimit: 0,
|
||||
NumTransactions: 0,
|
||||
NumL1Messages: 0,
|
||||
}
|
||||
version := uint8(1)
|
||||
var parentBatchHeader []byte
|
||||
var chunks [][]byte
|
||||
var skippedL1MessageBitmap []byte
|
||||
|
||||
batch := IScrollChainBatch{
|
||||
Blocks: []IScrollChainBlockContext{header},
|
||||
PrevStateRoot: common.Hash{},
|
||||
NewStateRoot: common.Hash{},
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
BatchIndex: 0,
|
||||
L2Transactions: make([]byte, 0),
|
||||
}
|
||||
|
||||
_, err = scrollChainABI.Pack("commitBatch", batch)
|
||||
_, err = scrollChainABI.Pack("commitBatch", version, parentBatchHeader, chunks, skippedL1MessageBitmap)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
@@ -78,14 +63,26 @@ func TestPackFinalizeBatchWithProof(t *testing.T) {
|
||||
l1RollupABI, err := ScrollChainMetaData.GetAbi()
|
||||
assert.NoError(err)
|
||||
|
||||
proof := make([]*big.Int, 10)
|
||||
instance := make([]*big.Int, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
proof[i] = big.NewInt(0)
|
||||
instance[i] = big.NewInt(0)
|
||||
}
|
||||
batchHeader := []byte{}
|
||||
prevStateRoot := common.Hash{}
|
||||
postStateRoot := common.Hash{}
|
||||
withdrawRoot := common.Hash{}
|
||||
aggrProof := []byte{}
|
||||
|
||||
_, err = l1RollupABI.Pack("finalizeBatchWithProof", common.Hash{}, proof, instance)
|
||||
_, err = l1RollupABI.Pack("finalizeBatchWithProof", batchHeader, prevStateRoot, postStateRoot, withdrawRoot, aggrProof)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestPackImportGenesisBatch(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
l1RollupABI, err := ScrollChainMetaData.GetAbi()
|
||||
assert.NoError(err)
|
||||
|
||||
batchHeader := []byte{}
|
||||
stateRoot := common.Hash{}
|
||||
|
||||
_, err = l1RollupABI.Pack("importGenesisBatch", batchHeader, stateRoot)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
@@ -95,7 +92,7 @@ func TestPackRelayL1Message(t *testing.T) {
|
||||
l2MessengerABI, err := L2ScrollMessengerMetaData.GetAbi()
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = l2MessengerABI.Pack("relayMessage", common.Address{}, common.Address{}, big.NewInt(0), big.NewInt(0), make([]byte, 0))
|
||||
_, err = l2MessengerABI.Pack("relayMessage", common.Address{}, common.Address{}, big.NewInt(0), big.NewInt(0), []byte{})
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
@@ -126,6 +123,6 @@ func TestPackImportBlock(t *testing.T) {
|
||||
|
||||
l1BlockContainerABI := L1BlockContainerABI
|
||||
|
||||
_, err := l1BlockContainerABI.Pack("importBlockHeader", common.Hash{}, make([]byte, 0), false)
|
||||
_, err := l1BlockContainerABI.Pack("importBlockHeader", common.Hash{}, []byte{}, false)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func action(ctx *cli.Context) error {
|
||||
log.Error("failed to create new l1 relayer", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
}
|
||||
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig)
|
||||
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig, false /* initGenesis */)
|
||||
if err != nil {
|
||||
log.Error("failed to create new l2 relayer", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
@@ -63,30 +62,15 @@ func action(ctx *cli.Context) error {
|
||||
// Start metrics server.
|
||||
metrics.Serve(subCtx, ctx)
|
||||
|
||||
// Init l2geth connection
|
||||
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
|
||||
if err != nil {
|
||||
log.Error("failed to connect l2 geth", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
l1relayer, err := relayer.NewLayer1Relayer(ctx.Context, db, cfg.L1Config.RelayerConfig)
|
||||
if err != nil {
|
||||
log.Error("failed to create new l1 relayer", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
}
|
||||
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig)
|
||||
if err != nil {
|
||||
log.Error("failed to create new l2 relayer", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Start l1relayer process
|
||||
go cutils.Loop(subCtx, 10*time.Second, l1relayer.ProcessSavedEvents)
|
||||
|
||||
// Start l2relayer process
|
||||
go cutils.Loop(subCtx, 2*time.Second, l2relayer.ProcessSavedEvents)
|
||||
|
||||
// Finish start all message relayer functions
|
||||
log.Info("Start message-relayer successfully")
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ func init() {
|
||||
app.Usage = "The Scroll Rollup Relayer"
|
||||
app.Version = version.Version
|
||||
app.Flags = append(app.Flags, cutils.CommonFlags...)
|
||||
app.Flags = append(app.Flags, cutils.RollupRelayerFlags...)
|
||||
app.Commands = []*cli.Command{}
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
return cutils.LogSetup(ctx)
|
||||
@@ -70,13 +71,20 @@ func action(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig)
|
||||
initGenesis := ctx.Bool(cutils.ImportGenesisFlag.Name)
|
||||
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig, initGenesis)
|
||||
if err != nil {
|
||||
log.Error("failed to create l2 relayer", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, l2relayer, db)
|
||||
chunkProposer := watcher.NewChunkProposer(subCtx, cfg.L2Config.ChunkProposerConfig, db)
|
||||
if err != nil {
|
||||
log.Error("failed to create chunkProposer", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, db)
|
||||
if err != nil {
|
||||
log.Error("failed to create batchProposer", "config file", cfgFile, "error", err)
|
||||
return err
|
||||
@@ -91,14 +99,14 @@ func action(ctx *cli.Context) error {
|
||||
log.Error("failed to get block number", "err", loopErr)
|
||||
return
|
||||
}
|
||||
l2watcher.TryFetchRunningMissingBlocks(ctx, number)
|
||||
l2watcher.TryFetchRunningMissingBlocks(number)
|
||||
})
|
||||
|
||||
// Batch proposer loop
|
||||
go cutils.Loop(subCtx, 2*time.Second, func() {
|
||||
batchProposer.TryProposeBatch()
|
||||
batchProposer.TryCommitBatches()
|
||||
})
|
||||
go cutils.Loop(subCtx, 2*time.Second, chunkProposer.TryProposeChunk)
|
||||
|
||||
go cutils.Loop(subCtx, 2*time.Second, batchProposer.TryProposeBatch)
|
||||
|
||||
go cutils.Loop(subCtx, 2*time.Second, l2relayer.ProcessPendingBatches)
|
||||
|
||||
go cutils.Loop(subCtx, 2*time.Second, l2relayer.ProcessCommittedBatches)
|
||||
|
||||
|
||||
@@ -72,18 +72,20 @@
|
||||
"1414141414141414141414141414141414141414141414141414141414141414"
|
||||
]
|
||||
},
|
||||
"chunk_proposer_config": {
|
||||
"max_tx_gas_per_chunk": 1123456,
|
||||
"max_l2_tx_num_per_chunk": 1123,
|
||||
"max_l1_commit_gas_per_chunk": 11234567,
|
||||
"max_l1_commit_calldata_size_per_chunk": 112345,
|
||||
"min_l1_commit_calldata_size_per_chunk": 11234,
|
||||
"chunk_timeout_sec": 300
|
||||
},
|
||||
"batch_proposer_config": {
|
||||
"proof_generation_freq": 1,
|
||||
"batch_gas_threshold": 3000000,
|
||||
"batch_tx_num_threshold": 44,
|
||||
"batch_time_sec": 300,
|
||||
"batch_commit_time_sec": 1200,
|
||||
"batch_blocks_limit": 100,
|
||||
"commit_tx_calldata_size_limit": 200000,
|
||||
"public_input_config": {
|
||||
"max_tx_num": 44,
|
||||
"padding_tx_hash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
"max_chunk_num_per_batch": 112,
|
||||
"max_l1_commit_gas_per_batch": 11234567,
|
||||
"max_l1_commit_calldata_size_per_batch": 112345,
|
||||
"min_chunk_num_per_batch": 11,
|
||||
"batch_timeout_sec": 300
|
||||
}
|
||||
},
|
||||
"db_config": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module scroll-tech/bridge
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/agiledragon/gomonkey/v2 v2.9.0
|
||||
@@ -9,14 +9,13 @@ require (
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||
github.com/pressly/goose/v3 v3.7.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230508165858-27a3830afa61
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230613025759-f055f50f9d56
|
||||
github.com/smartystreets/goconvey v1.8.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
|
||||
golang.org/x/sync v0.1.0
|
||||
gorm.io/driver/postgres v1.5.0
|
||||
gorm.io/gorm v1.25.1
|
||||
modernc.org/mathutil v1.4.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -48,7 +47,6 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rjeczalik/notify v0.9.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
@@ -62,9 +60,9 @@ require (
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
|
||||
@@ -117,7 +117,6 @@ github.com/pressly/goose/v3 v3.7.0 h1:jblaZul15uCIEKHRu5KUdA+5wDA7E60JC0TOthdrtf
|
||||
github.com/pressly/goose/v3 v3.7.0/go.mod h1:N5gqPdIzdxf3BiPWdmoPreIwHStkxsvKWE5xjUvfYNk=
|
||||
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@@ -130,8 +129,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
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.20230508165858-27a3830afa61 h1:4fslVpNOPLeJPYX3tivrVWgqNvChPs7/y9OqWvQSNCw=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230508165858-27a3830afa61/go.mod h1:45PZqlQCqV0dU4o4+SE8LoJLEvXkK5j45ligvbih9QY=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230613025759-f055f50f9d56 h1:Cqj7haxwvzI2O4n9ZZ25helShzFGCy7Z/B+FFSBFHNI=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230613025759-f055f50f9d56/go.mod h1:45PZqlQCqV0dU4o4+SE8LoJLEvXkK5j45ligvbih9QY=
|
||||
github.com/scroll-tech/zktrie v0.5.3 h1:jjzQchGU6XPL5s1C5bwwivSadefSRuYASE9OL7UKAdE=
|
||||
github.com/scroll-tech/zktrie v0.5.3/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
@@ -173,8 +172,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -202,8 +201,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -211,8 +210,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -246,7 +245,6 @@ modernc.org/cc/v3 v3.36.1 h1:CICrjwr/1M4+6OQ4HJZ/AHxjcwe67r5vPUF518MkO8A=
|
||||
modernc.org/ccgo/v3 v3.16.8 h1:G0QNlTqI5uVgczBWfGKs7B++EPwCfXPWGD2MdeKloDs=
|
||||
modernc.org/libc v1.16.19 h1:S8flPn5ZeXx6iw/8yNa986hwTQDrY8RXU7tObZuAozo=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// DBConfig db config
|
||||
type DBConfig struct {
|
||||
// data source name
|
||||
@@ -15,19 +9,3 @@ type DBConfig struct {
|
||||
MaxOpenNum int `json:"maxOpenNum"`
|
||||
MaxIdleNum int `json:"maxIdleNum"`
|
||||
}
|
||||
|
||||
// NewDBConfig returns a new instance of Config.
|
||||
func NewDBConfig(file string) (*DBConfig, error) {
|
||||
buf, err := os.ReadFile(filepath.Clean(file))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &DBConfig{}
|
||||
err = json.Unmarshal(buf, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
|
||||
"scroll-tech/bridge/internal/types"
|
||||
)
|
||||
|
||||
// L2Config loads l2geth configuration items.
|
||||
@@ -22,28 +20,27 @@ type L2Config struct {
|
||||
WithdrawTrieRootSlot common.Hash `json:"withdraw_trie_root_slot,omitempty"`
|
||||
// The relayer config
|
||||
RelayerConfig *RelayerConfig `json:"relayer_config"`
|
||||
// The chunk_proposer config
|
||||
ChunkProposerConfig *ChunkProposerConfig `json:"chunk_proposer_config"`
|
||||
// The batch_proposer config
|
||||
BatchProposerConfig *BatchProposerConfig `json:"batch_proposer_config"`
|
||||
}
|
||||
|
||||
// BatchProposerConfig loads l2watcher batch_proposer configuration items.
|
||||
type BatchProposerConfig struct {
|
||||
// Proof generation frequency, generating proof every k blocks
|
||||
ProofGenerationFreq uint64 `json:"proof_generation_freq"`
|
||||
// Txnum threshold in a batch
|
||||
BatchTxNumThreshold uint64 `json:"batch_tx_num_threshold"`
|
||||
// Gas threshold in a batch
|
||||
BatchGasThreshold uint64 `json:"batch_gas_threshold"`
|
||||
// Time waited to generate a batch even if gas_threshold not met
|
||||
BatchTimeSec uint64 `json:"batch_time_sec"`
|
||||
// Time waited to commit batches before the calldata met CommitTxCalldataSizeLimit
|
||||
BatchCommitTimeSec uint64 `json:"batch_commit_time_sec"`
|
||||
// Max number of blocks in a batch
|
||||
BatchBlocksLimit uint64 `json:"batch_blocks_limit"`
|
||||
// Commit tx calldata size limit in bytes, target to cap the gas use of commit tx at 2M gas
|
||||
CommitTxCalldataSizeLimit uint64 `json:"commit_tx_calldata_size_limit"`
|
||||
// Commit tx calldata min size limit in bytes
|
||||
CommitTxCalldataMinSize uint64 `json:"commit_tx_calldata_min_size,omitempty"`
|
||||
// The public input hash config
|
||||
PublicInputConfig *types.PublicInputHashConfig `json:"public_input_config"`
|
||||
// ChunkProposerConfig loads chunk_proposer configuration items.
|
||||
type ChunkProposerConfig struct {
|
||||
MaxTxGasPerChunk uint64 `json:"max_tx_gas_per_chunk"`
|
||||
MaxL2TxNumPerChunk uint64 `json:"max_l2_tx_num_per_chunk"`
|
||||
MaxL1CommitGasPerChunk uint64 `json:"max_l1_commit_gas_per_chunk"`
|
||||
MaxL1CommitCalldataSizePerChunk uint64 `json:"max_l1_commit_calldata_size_per_chunk"`
|
||||
MinL1CommitCalldataSizePerChunk uint64 `json:"min_l1_commit_calldata_size_per_chunk"`
|
||||
ChunkTimeoutSec uint64 `json:"chunk_timeout_sec"`
|
||||
}
|
||||
|
||||
// BatchProposerConfig loads batch_proposer configuration items.
|
||||
type BatchProposerConfig struct {
|
||||
MaxChunkNumPerBatch uint64 `json:"max_chunk_num_per_batch"`
|
||||
MaxL1CommitGasPerBatch uint64 `json:"max_l1_commit_gas_per_batch"`
|
||||
MaxL1CommitCalldataSizePerBatch uint64 `json:"max_l1_commit_calldata_size_per_batch"`
|
||||
MinChunkNumPerBatch uint64 `json:"min_chunk_num_per_batch"`
|
||||
BatchTimeoutSec uint64 `json:"batch_timeout_sec"`
|
||||
}
|
||||
|
||||
@@ -3,19 +3,17 @@ package relayer
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
gethMetrics "github.com/scroll-tech/go-ethereum/metrics"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gorm.io/gorm"
|
||||
"modernc.org/mathutil"
|
||||
|
||||
"scroll-tech/common/metrics"
|
||||
"scroll-tech/common/types"
|
||||
@@ -25,14 +23,11 @@ import (
|
||||
"scroll-tech/bridge/internal/controller/sender"
|
||||
"scroll-tech/bridge/internal/orm"
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
"scroll-tech/bridge/internal/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
bridgeL2MsgsRelayedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/msgs/relayed/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesFinalizedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/finalized/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesCommittedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/committed/total", metrics.ScrollRegistry)
|
||||
bridgeL2MsgsRelayedConfirmedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/msgs/relayed/confirmed/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesFinalizedConfirmedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/finalized/confirmed/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesCommittedConfirmedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/committed/confirmed/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesSkippedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/skipped/total", metrics.ScrollRegistry)
|
||||
@@ -49,9 +44,11 @@ type Layer2Relayer struct {
|
||||
|
||||
l2Client *ethclient.Client
|
||||
|
||||
blockBatchOrm *orm.BlockBatch
|
||||
blockTraceOrm *orm.BlockTrace
|
||||
l2MessageOrm *orm.L2Message
|
||||
db *gorm.DB
|
||||
batchOrm *orm.Batch
|
||||
chunkOrm *orm.Chunk
|
||||
l2BlockOrm *orm.L2Block
|
||||
l2MessageOrm *orm.L2Message
|
||||
|
||||
cfg *config.RelayerConfig
|
||||
|
||||
@@ -75,8 +72,8 @@ type Layer2Relayer struct {
|
||||
processingMessage sync.Map
|
||||
|
||||
// A list of processing batches commitment.
|
||||
// key(string): confirmation ID, value([]string): batch hashes.
|
||||
processingBatchesCommitment sync.Map
|
||||
// key(string): confirmation ID, value(string): batch hash.
|
||||
processingCommitment sync.Map
|
||||
|
||||
// A list of processing batch finalization.
|
||||
// key(string): confirmation ID, value(string): batch hash.
|
||||
@@ -84,7 +81,7 @@ type Layer2Relayer struct {
|
||||
}
|
||||
|
||||
// NewLayer2Relayer will return a new instance of Layer2RelayerClient
|
||||
func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.DB, cfg *config.RelayerConfig) (*Layer2Relayer, error) {
|
||||
func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.DB, cfg *config.RelayerConfig, initGenesis bool) (*Layer2Relayer, error) {
|
||||
// @todo use different sender for relayer, block commit and proof finalize
|
||||
messageSender, err := sender.NewSender(ctx, cfg.SenderConfig, cfg.MessageSenderPrivateKeys)
|
||||
if err != nil {
|
||||
@@ -121,10 +118,12 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.
|
||||
|
||||
layer2Relayer := &Layer2Relayer{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
|
||||
blockBatchOrm: orm.NewBlockBatch(db),
|
||||
l2MessageOrm: orm.NewL2Message(db),
|
||||
blockTraceOrm: orm.NewBlockTrace(db),
|
||||
batchOrm: orm.NewBatch(db),
|
||||
l2MessageOrm: orm.NewL2Message(db),
|
||||
l2BlockOrm: orm.NewL2Block(db),
|
||||
chunkOrm: orm.NewChunk(db),
|
||||
|
||||
l2Client: l2Client,
|
||||
|
||||
@@ -142,134 +141,135 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.
|
||||
minGasPrice: minGasPrice,
|
||||
gasPriceDiff: gasPriceDiff,
|
||||
|
||||
cfg: cfg,
|
||||
processingMessage: sync.Map{},
|
||||
processingBatchesCommitment: sync.Map{},
|
||||
processingFinalization: sync.Map{},
|
||||
cfg: cfg,
|
||||
processingMessage: sync.Map{},
|
||||
processingCommitment: sync.Map{},
|
||||
processingFinalization: sync.Map{},
|
||||
}
|
||||
|
||||
// Initialize genesis before we do anything else
|
||||
if initGenesis {
|
||||
if err := layer2Relayer.initializeGenesis(); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize and commit genesis batch, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
go layer2Relayer.handleConfirmLoop(ctx)
|
||||
return layer2Relayer, nil
|
||||
}
|
||||
|
||||
const processMsgLimit = 100
|
||||
|
||||
// ProcessSavedEvents relays saved un-processed cross-domain transactions to desired blockchain
|
||||
func (r *Layer2Relayer) ProcessSavedEvents() {
|
||||
batch, err := r.blockBatchOrm.GetLatestBatchByRollupStatus([]types.RollupStatus{types.RollupFinalized})
|
||||
if err != nil {
|
||||
log.Error("GetLatestFinalizedBatch failed", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// msgs are sorted by nonce in increasing order
|
||||
fields := map[string]interface{}{
|
||||
"status": int(types.MsgPending),
|
||||
"height <= (?)": batch.EndBlockNumber,
|
||||
}
|
||||
orderByList := []string{
|
||||
"nonce ASC",
|
||||
}
|
||||
limit := processMsgLimit
|
||||
|
||||
msgs, err := r.l2MessageOrm.GetL2Messages(fields, orderByList, limit)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch unprocessed L2 messages", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// process messages in batches
|
||||
batchSize := mathutil.Min((runtime.GOMAXPROCS(0)+1)/2, r.messageSender.NumberOfAccounts())
|
||||
for size := 0; len(msgs) > 0; msgs = msgs[size:] {
|
||||
if size = len(msgs); size > batchSize {
|
||||
size = batchSize
|
||||
}
|
||||
var g errgroup.Group
|
||||
for _, msg := range msgs[:size] {
|
||||
msg := msg
|
||||
g.Go(func() error {
|
||||
return r.processSavedEvent(&msg)
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
|
||||
log.Error("failed to process l2 saved event", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) processSavedEvent(msg *orm.L2Message) error {
|
||||
// @todo fetch merkle proof from l2geth
|
||||
log.Info("Processing L2 Message", "msg.nonce", msg.Nonce, "msg.height", msg.Height)
|
||||
|
||||
// Get the block info that contains the message
|
||||
blockInfos, err := r.blockTraceOrm.GetL2BlockInfos(map[string]interface{}{"number": msg.Height}, nil, 0)
|
||||
if err != nil {
|
||||
log.Error("Failed to GetL2BlockInfos from DB", "number", msg.Height)
|
||||
}
|
||||
if len(blockInfos) == 0 {
|
||||
return errors.New("get block trace len is 0, exit")
|
||||
}
|
||||
|
||||
blockInfo := blockInfos[0]
|
||||
if blockInfo.BatchHash == "" {
|
||||
log.Error("Block has not been batched yet", "number", blockInfo.Number, "msg.nonce", msg.Nonce)
|
||||
func (r *Layer2Relayer) initializeGenesis() error {
|
||||
if count, err := r.batchOrm.GetBatchCount(r.ctx); err != nil {
|
||||
return fmt.Errorf("failed to get batch count: %v", err)
|
||||
} else if count > 0 {
|
||||
log.Info("genesis already imported", "batch count", count)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: rebuild the withdraw trie to generate the merkle proof
|
||||
proof := bridgeAbi.IL1ScrollMessengerL2MessageProof{
|
||||
BatchHash: common.HexToHash(blockInfo.BatchHash),
|
||||
MerkleProof: make([]byte, 0),
|
||||
}
|
||||
from := common.HexToAddress(msg.Sender)
|
||||
target := common.HexToAddress(msg.Target)
|
||||
value, ok := big.NewInt(0).SetString(msg.Value, 10)
|
||||
if !ok {
|
||||
// @todo maybe panic?
|
||||
log.Error("Failed to parse message value", "msg.nonce", msg.Nonce, "msg.height", msg.Height)
|
||||
// TODO: need to skip this message by changing its status to MsgError
|
||||
}
|
||||
msgNonce := big.NewInt(int64(msg.Nonce))
|
||||
calldata := common.Hex2Bytes(msg.Calldata)
|
||||
data, err := r.l1MessengerABI.Pack("relayMessageWithProof", from, target, value, msgNonce, calldata, proof)
|
||||
genesis, err := r.l2Client.HeaderByNumber(r.ctx, big.NewInt(0))
|
||||
if err != nil {
|
||||
log.Error("Failed to pack relayMessageWithProof", "msg.nonce", msg.Nonce, "err", err)
|
||||
// TODO: need to skip this message by changing its status to MsgError
|
||||
return err
|
||||
return fmt.Errorf("failed to retrieve L2 genesis header: %v", err)
|
||||
}
|
||||
|
||||
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), data, r.minGasLimitForMessageRelay)
|
||||
if err != nil && errors.Is(err, ErrExecutionRevertedMessageExpired) {
|
||||
return r.l2MessageOrm.UpdateLayer2Status(r.ctx, msg.MsgHash, types.MsgExpired)
|
||||
log.Info("retrieved L2 genesis header", "hash", genesis.Hash().String())
|
||||
|
||||
chunk := &bridgeTypes.Chunk{
|
||||
Blocks: []*bridgeTypes.WrappedBlock{{
|
||||
Header: genesis,
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
}},
|
||||
}
|
||||
if err != nil && errors.Is(err, ErrExecutionRevertedAlreadySuccessExecuted) {
|
||||
return r.l2MessageOrm.UpdateLayer2Status(r.ctx, msg.MsgHash, types.MsgConfirmed)
|
||||
}
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
|
||||
log.Error("Failed to send relayMessageWithProof tx to layer1 ", "msg.height", msg.Height, "msg.MsgHash", msg.MsgHash, "err", err)
|
||||
|
||||
err = r.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
var dbChunk *orm.Chunk
|
||||
dbChunk, err = r.chunkOrm.InsertChunk(r.ctx, chunk, dbTX)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert chunk: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
bridgeL2MsgsRelayedTotalCounter.Inc(1)
|
||||
log.Info("relayMessageWithProof to layer1", "msgHash", msg.MsgHash, "txhash", hash.String())
|
||||
|
||||
// save status in db
|
||||
// @todo handle db error
|
||||
err = r.l2MessageOrm.UpdateLayer2StatusAndLayer1Hash(r.ctx, msg.MsgHash, types.MsgSubmitted, hash.String())
|
||||
if err = r.chunkOrm.UpdateProvingStatus(r.ctx, dbChunk.Hash, types.ProvingTaskVerified, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update genesis chunk proving status: %v", err)
|
||||
}
|
||||
|
||||
var batch *orm.Batch
|
||||
batch, err = r.batchOrm.InsertBatch(r.ctx, 0, 0, dbChunk.Hash, dbChunk.Hash, []*bridgeTypes.Chunk{chunk}, dbTX)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert batch: %v", err)
|
||||
}
|
||||
|
||||
if err = r.chunkOrm.UpdateBatchHashInRange(r.ctx, 0, 0, batch.Hash, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update batch hash for chunks: %v", err)
|
||||
}
|
||||
|
||||
if err = r.batchOrm.UpdateProvingStatus(r.ctx, batch.Hash, types.ProvingTaskVerified, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update genesis batch proving status: %v", err)
|
||||
}
|
||||
|
||||
if err = r.batchOrm.UpdateRollupStatus(r.ctx, batch.Hash, types.RollupFinalized, dbTX); err != nil {
|
||||
return fmt.Errorf("failed to update genesis batch rollup status: %v", err)
|
||||
}
|
||||
|
||||
// commit genesis batch on L1
|
||||
// note: we do this inside the DB transaction so that we can revert all DB changes if this step fails
|
||||
return r.commitGenesisBatch(batch.Hash, batch.BatchHeader, common.HexToHash(batch.StateRoot))
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error("UpdateLayer2StatusAndLayer1Hash failed", "msgHash", msg.MsgHash, "err", err)
|
||||
return err
|
||||
return fmt.Errorf("update genesis transaction failed: %v", err)
|
||||
}
|
||||
r.processingMessage.Store(msg.MsgHash, msg.MsgHash)
|
||||
|
||||
log.Info("successfully imported genesis chunk and batch")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte, stateRoot common.Hash) error {
|
||||
// encode "importGenesisBatch" transaction calldata
|
||||
calldata, err := r.l1RollupABI.Pack("importGenesisBatch", batchHeader, stateRoot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pack importGenesisBatch with batch header: %v and state root: %v. error: %v", common.Bytes2Hex(batchHeader), stateRoot, err)
|
||||
}
|
||||
|
||||
// submit genesis batch to L1 rollup contract
|
||||
txHash, err := r.rollupSender.SendTransaction(batchHash, &r.cfg.RollupContractAddress, big.NewInt(0), calldata, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send import genesis batch tx to L1, error: %v", err)
|
||||
}
|
||||
log.Info("importGenesisBatch transaction sent", "contract", r.cfg.RollupContractAddress, "txHash", txHash.String(), "batchHash", batchHash)
|
||||
|
||||
// wait for confirmation
|
||||
// we assume that no other transactions are sent before initializeGenesis completes
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
// print progress
|
||||
case <-ticker.C:
|
||||
log.Info("Waiting for confirmation", "pending count", r.rollupSender.PendingCount())
|
||||
|
||||
// timeout
|
||||
case <-time.After(5 * time.Minute):
|
||||
return fmt.Errorf("import genesis timeout after 5 minutes, original txHash: %v", txHash.String())
|
||||
|
||||
// handle confirmation
|
||||
case confirmation := <-r.rollupSender.ConfirmChan():
|
||||
if confirmation.ID != batchHash {
|
||||
return fmt.Errorf("unexpected import genesis confirmation id, expected: %v, got: %v", batchHash, confirmation.ID)
|
||||
}
|
||||
if !confirmation.IsSuccessful {
|
||||
return fmt.Errorf("import genesis batch tx failed")
|
||||
}
|
||||
log.Info("Successfully committed genesis batch on L1", "txHash", confirmation.TxHash.String())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessGasPriceOracle imports gas price to layer1
|
||||
func (r *Layer2Relayer) ProcessGasPriceOracle() {
|
||||
batch, err := r.blockBatchOrm.GetLatestBatch()
|
||||
batch, err := r.batchOrm.GetLatestBatch(r.ctx)
|
||||
if err != nil {
|
||||
log.Error("Failed to GetLatestBatch", "err", err)
|
||||
return
|
||||
@@ -300,7 +300,7 @@ func (r *Layer2Relayer) ProcessGasPriceOracle() {
|
||||
return
|
||||
}
|
||||
|
||||
err = r.blockBatchOrm.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, batch.Hash, types.GasOracleImporting, hash.String())
|
||||
err = r.batchOrm.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, batch.Hash, types.GasOracleImporting, hash.String())
|
||||
if err != nil {
|
||||
log.Error("UpdateGasOracleStatusAndOracleTxHash failed", "batch.Hash", batch.Hash, "err", err)
|
||||
return
|
||||
@@ -311,93 +311,119 @@ func (r *Layer2Relayer) ProcessGasPriceOracle() {
|
||||
}
|
||||
}
|
||||
|
||||
// SendCommitTx sends commitBatches tx to L1.
|
||||
func (r *Layer2Relayer) SendCommitTx(batchData []*bridgeTypes.BatchData) error {
|
||||
if len(batchData) == 0 {
|
||||
log.Error("SendCommitTx receives empty batch")
|
||||
return nil
|
||||
}
|
||||
|
||||
// pack calldata
|
||||
commitBatches := make([]bridgeAbi.IScrollChainBatch, len(batchData))
|
||||
for i, batch := range batchData {
|
||||
commitBatches[i] = batch.Batch
|
||||
}
|
||||
calldata, err := r.l1RollupABI.Pack("commitBatches", commitBatches)
|
||||
// ProcessPendingBatches processes the pending batches by sending commitBatch transactions to layer 1.
|
||||
func (r *Layer2Relayer) ProcessPendingBatches() {
|
||||
// get pending batches from database in ascending order by their index.
|
||||
pendingBatches, err := r.batchOrm.GetPendingBatches(r.ctx, 10)
|
||||
if err != nil {
|
||||
log.Error("Failed to pack commitBatches",
|
||||
"error", err,
|
||||
"start_batch_index", commitBatches[0].BatchIndex,
|
||||
"end_batch_index", commitBatches[len(commitBatches)-1].BatchIndex)
|
||||
return err
|
||||
log.Error("Failed to fetch pending L2 batches", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// generate a unique txID and send transaction
|
||||
var bytes []byte
|
||||
for _, batch := range batchData {
|
||||
bytes = append(bytes, batch.Hash().Bytes()...)
|
||||
}
|
||||
txID := crypto.Keccak256Hash(bytes).String()
|
||||
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), calldata, 0)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
|
||||
log.Error("Failed to send commitBatches tx to layer1 ", "err", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
bridgeL2BatchesCommittedTotalCounter.Inc(int64(len(commitBatches)))
|
||||
log.Info("Sent the commitBatches tx to layer1",
|
||||
"tx_hash", txHash.Hex(),
|
||||
"start_batch_index", commitBatches[0].BatchIndex,
|
||||
"end_batch_index", commitBatches[len(commitBatches)-1].BatchIndex)
|
||||
|
||||
// record and sync with db, @todo handle db error
|
||||
batchHashes := make([]string, len(batchData))
|
||||
for i, batch := range batchData {
|
||||
batchHashes[i] = batch.Hash().Hex()
|
||||
err = r.blockBatchOrm.UpdateCommitTxHashAndRollupStatus(r.ctx, batchHashes[i], txHash.String(), types.RollupCommitting)
|
||||
for _, batch := range pendingBatches {
|
||||
// get current header and parent header.
|
||||
currentBatchHeader, err := bridgeTypes.DecodeBatchHeader(batch.BatchHeader)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitTxHashAndRollupStatus failed", "hash", batchHashes[i], "index", batch.Batch.BatchIndex, "err", err)
|
||||
log.Error("Failed to decode batch header", "index", batch.Index, "error", err)
|
||||
return
|
||||
}
|
||||
parentBatch := &orm.Batch{}
|
||||
if batch.Index > 0 {
|
||||
parentBatch, err = r.batchOrm.GetBatchByIndex(r.ctx, batch.Index-1)
|
||||
if err != nil {
|
||||
log.Error("Failed to get parent batch header", "index", batch.Index-1, "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// get the chunks for the batch
|
||||
startChunkIndex := batch.StartChunkIndex
|
||||
endChunkIndex := batch.EndChunkIndex
|
||||
dbChunks, err := r.chunkOrm.GetChunksInRange(r.ctx, startChunkIndex, endChunkIndex)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch chunks",
|
||||
"start index", startChunkIndex,
|
||||
"end index", endChunkIndex, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
encodedChunks := make([][]byte, len(dbChunks))
|
||||
for i, c := range dbChunks {
|
||||
var wrappedBlocks []*bridgeTypes.WrappedBlock
|
||||
wrappedBlocks, err = r.l2BlockOrm.GetL2BlocksInRange(r.ctx, c.StartBlockNumber, c.EndBlockNumber)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch wrapped blocks",
|
||||
"start number", c.StartBlockNumber,
|
||||
"end number", c.EndBlockNumber, "error", err)
|
||||
return
|
||||
}
|
||||
chunk := &bridgeTypes.Chunk{
|
||||
Blocks: wrappedBlocks,
|
||||
}
|
||||
var chunkBytes []byte
|
||||
chunkBytes, err = chunk.Encode(c.TotalL1MessagesPoppedBefore)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode chunk", "error", err)
|
||||
return
|
||||
}
|
||||
encodedChunks[i] = chunkBytes
|
||||
}
|
||||
|
||||
calldata, err := r.l1RollupABI.Pack("commitBatch", currentBatchHeader.Version(), parentBatch.BatchHeader, encodedChunks, currentBatchHeader.SkippedL1MessageBitmap())
|
||||
if err != nil {
|
||||
log.Error("Failed to pack commitBatch", "index", batch.Index, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// send transaction
|
||||
txID := batch.Hash + "-commit"
|
||||
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), calldata, 0)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
|
||||
log.Error("Failed to send commitBatch tx to layer1 ", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = r.batchOrm.UpdateCommitTxHashAndRollupStatus(r.ctx, batch.Hash, txHash.String(), types.RollupCommitting)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitTxHashAndRollupStatus failed", "hash", batch.Hash, "index", batch.Index, "err", err)
|
||||
return
|
||||
}
|
||||
bridgeL2BatchesCommittedTotalCounter.Inc(1)
|
||||
r.processingCommitment.Store(txID, batch.Hash)
|
||||
log.Info("Sent the commitBatch tx to layer1", "batch index", batch.Index, "batch hash", batch.Hash, "tx hash", txHash.Hex())
|
||||
}
|
||||
r.processingBatchesCommitment.Store(txID, batchHashes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessCommittedBatches submit proof to layer 1 rollup contract
|
||||
func (r *Layer2Relayer) ProcessCommittedBatches() {
|
||||
// set skipped batches in a single db operation
|
||||
if count, err := r.blockBatchOrm.UpdateSkippedBatches(); err != nil {
|
||||
if count, err := r.batchOrm.UpdateSkippedBatches(r.ctx); err != nil {
|
||||
log.Error("UpdateSkippedBatches failed", "err", err)
|
||||
// continue anyway
|
||||
} else if count > 0 {
|
||||
bridgeL2BatchesSkippedTotalCounter.Inc(count)
|
||||
bridgeL2BatchesSkippedTotalCounter.Inc(int64(count))
|
||||
log.Info("Skipping batches", "count", count)
|
||||
}
|
||||
|
||||
// batches are sorted by batch index in increasing order
|
||||
batchHashes, err := r.blockBatchOrm.GetBlockBatchesHashByRollupStatus(types.RollupCommitted, 1)
|
||||
// retrieves the earliest batch whose rollup status is 'committed'
|
||||
fields := map[string]interface{}{
|
||||
"rollup_status": types.RollupCommitted,
|
||||
}
|
||||
orderByList := []string{"index ASC"}
|
||||
limit := 1
|
||||
batches, err := r.batchOrm.GetBatches(r.ctx, fields, orderByList, limit)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch committed L2 batches", "err", err)
|
||||
return
|
||||
}
|
||||
if len(batchHashes) == 0 {
|
||||
return
|
||||
}
|
||||
hash := batchHashes[0]
|
||||
// @todo add support to relay multiple batches
|
||||
|
||||
batches, err := r.blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": hash}, nil, 1)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch committed L2 batch", "hash", hash, "err", err)
|
||||
return
|
||||
}
|
||||
if len(batches) == 0 {
|
||||
log.Error("Unexpected result for GetBlockBatches", "hash", hash, "len", 0)
|
||||
if len(batches) != 1 {
|
||||
log.Warn("Unexpected result for GetBlockBatches", "number of batches", len(batches))
|
||||
return
|
||||
}
|
||||
|
||||
batch := batches[0]
|
||||
hash := batch.Hash
|
||||
status := types.ProvingStatus(batch.ProvingStatus)
|
||||
switch status {
|
||||
case types.ProvingTaskUnassigned, types.ProvingTaskAssigned:
|
||||
@@ -409,75 +435,54 @@ func (r *Layer2Relayer) ProcessCommittedBatches() {
|
||||
return
|
||||
case types.ProvingTaskFailed, types.ProvingTaskSkipped:
|
||||
// note: this is covered by UpdateSkippedBatches, but we keep it for completeness's sake
|
||||
if err = r.blockBatchOrm.UpdateRollupStatus(r.ctx, hash, types.RollupFinalizationSkipped); err != nil {
|
||||
if err = r.batchOrm.UpdateRollupStatus(r.ctx, hash, types.RollupFinalizationSkipped); err != nil {
|
||||
log.Warn("UpdateRollupStatus failed", "hash", hash, "err", err)
|
||||
}
|
||||
case types.ProvingTaskVerified:
|
||||
log.Info("Start to roll up zk proof", "hash", hash)
|
||||
success := false
|
||||
|
||||
rollupStatues := []types.RollupStatus{
|
||||
types.RollupFinalizing,
|
||||
types.RollupFinalized,
|
||||
}
|
||||
previousBatch, err := r.blockBatchOrm.GetLatestBatchByRollupStatus(rollupStatues)
|
||||
// skip submitting proof
|
||||
if err == nil && uint64(batch.CreatedAt.Sub(previousBatch.CreatedAt).Seconds()) < r.cfg.FinalizeBatchIntervalSec {
|
||||
log.Info(
|
||||
"Not enough time passed, skipping",
|
||||
"hash", hash,
|
||||
"createdAt", batch.CreatedAt,
|
||||
"lastFinalizingHash", previousBatch.Hash,
|
||||
"lastFinalizingStatus", previousBatch.RollupStatus,
|
||||
"lastFinalizingCreatedAt", previousBatch.CreatedAt,
|
||||
)
|
||||
|
||||
if err = r.blockBatchOrm.UpdateRollupStatus(r.ctx, hash, types.RollupFinalizationSkipped); err != nil {
|
||||
log.Warn("UpdateRollupStatus failed", "hash", hash, "err", err)
|
||||
} else {
|
||||
success = true
|
||||
var parentBatchStateRoot string
|
||||
if batch.Index > 0 {
|
||||
var parentBatch *orm.Batch
|
||||
parentBatch, err = r.batchOrm.GetBatchByIndex(r.ctx, batch.Index-1)
|
||||
// handle unexpected db error
|
||||
if err != nil {
|
||||
log.Error("Failed to get batch", "index", batch.Index-1, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// handle unexpected db error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error("Failed to get latest finalized batch", "err", err)
|
||||
return
|
||||
parentBatchStateRoot = parentBatch.StateRoot
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// TODO: need to revisit this and have a more fine-grained error handling
|
||||
if !success {
|
||||
log.Info("Failed to upload the proof, change rollup status to FinalizationSkipped", "hash", hash)
|
||||
if err = r.blockBatchOrm.UpdateRollupStatus(r.ctx, hash, types.RollupFinalizationSkipped); err != nil {
|
||||
if err = r.batchOrm.UpdateRollupStatus(r.ctx, hash, types.RollupFinalizationSkipped); err != nil {
|
||||
log.Warn("UpdateRollupStatus failed", "hash", hash, "err", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
proofBuffer, icBuffer, err := r.blockBatchOrm.GetVerifiedProofAndInstanceCommitmentsByHash(hash)
|
||||
aggProof, err := r.batchOrm.GetVerifiedProofByHash(r.ctx, hash)
|
||||
if err != nil {
|
||||
log.Warn("fetch get proof by hash failed", "hash", hash, "err", err)
|
||||
return
|
||||
}
|
||||
if proofBuffer == nil || icBuffer == nil {
|
||||
log.Warn("proof or instance not ready", "hash", hash)
|
||||
return
|
||||
}
|
||||
if len(proofBuffer)%32 != 0 {
|
||||
log.Error("proof buffer has wrong length", "hash", hash, "length", len(proofBuffer))
|
||||
return
|
||||
}
|
||||
if len(icBuffer)%32 != 0 {
|
||||
log.Warn("instance buffer has wrong length", "hash", hash, "length", len(icBuffer))
|
||||
log.Warn("get verified proof by hash failed", "hash", hash, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
proof := utils.BufferToUint256Le(proofBuffer)
|
||||
instance := utils.BufferToUint256Le(icBuffer)
|
||||
data, err := r.l1RollupABI.Pack("finalizeBatchWithProof", common.HexToHash(hash), proof, instance)
|
||||
if err = aggProof.SanityCheck(); err != nil {
|
||||
log.Warn("agg_proof sanity check fails", "hash", hash, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := r.l1RollupABI.Pack(
|
||||
"finalizeBatchWithProof",
|
||||
batch.BatchHeader,
|
||||
common.HexToHash(parentBatchStateRoot),
|
||||
common.HexToHash(batch.StateRoot),
|
||||
common.HexToHash(batch.WithdrawRoot),
|
||||
aggProof.Proof,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("Pack finalizeBatchWithProof failed", "err", err)
|
||||
return
|
||||
@@ -489,17 +494,20 @@ func (r *Layer2Relayer) ProcessCommittedBatches() {
|
||||
finalizeTxHash := &txHash
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
|
||||
log.Error("finalizeBatchWithProof in layer1 failed", "hash", hash, "err", err)
|
||||
log.Error("finalizeBatchWithProof in layer1 failed",
|
||||
"index", batch.Index, "hash", batch.Hash, "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
bridgeL2BatchesFinalizedTotalCounter.Inc(1)
|
||||
log.Info("finalizeBatchWithProof in layer1", "batch_hash", hash, "tx_hash", hash)
|
||||
log.Info("finalizeBatchWithProof in layer1", "index", batch.Index, "batch hash", batch.Hash, "tx hash", hash)
|
||||
|
||||
// record and sync with db, @todo handle db error
|
||||
err = r.blockBatchOrm.UpdateFinalizeTxHashAndRollupStatus(r.ctx, hash, finalizeTxHash.String(), types.RollupFinalizing)
|
||||
err = r.batchOrm.UpdateFinalizeTxHashAndRollupStatus(r.ctx, hash, finalizeTxHash.String(), types.RollupFinalizing)
|
||||
if err != nil {
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed", "batch_hash", hash, "err", err)
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed",
|
||||
"index", batch.Index, "batch hash", batch.Hash,
|
||||
"tx hash", finalizeTxHash.String(), "err", err)
|
||||
}
|
||||
success = true
|
||||
r.processingFinalization.Store(txID, hash)
|
||||
@@ -513,29 +521,10 @@ func (r *Layer2Relayer) ProcessCommittedBatches() {
|
||||
|
||||
func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
|
||||
transactionType := "Unknown"
|
||||
// check whether it is message relay transaction
|
||||
if msgHash, ok := r.processingMessage.Load(confirmation.ID); ok {
|
||||
transactionType = "MessageRelay"
|
||||
var status types.MsgStatus
|
||||
if confirmation.IsSuccessful {
|
||||
status = types.MsgConfirmed
|
||||
} else {
|
||||
status = types.MsgRelayFailed
|
||||
log.Warn("transaction confirmed but failed in layer1", "confirmation", confirmation)
|
||||
}
|
||||
// @todo handle db error
|
||||
err := r.l2MessageOrm.UpdateLayer2StatusAndLayer1Hash(r.ctx, msgHash.(string), status, confirmation.TxHash.String())
|
||||
if err != nil {
|
||||
log.Warn("UpdateLayer2StatusAndLayer1Hash failed", "msgHash", msgHash.(string), "err", err)
|
||||
}
|
||||
bridgeL2MsgsRelayedConfirmedTotalCounter.Inc(1)
|
||||
r.processingMessage.Delete(confirmation.ID)
|
||||
}
|
||||
|
||||
// check whether it is CommitBatches transaction
|
||||
if batchBatches, ok := r.processingBatchesCommitment.Load(confirmation.ID); ok {
|
||||
if batchHash, ok := r.processingCommitment.Load(confirmation.ID); ok {
|
||||
transactionType = "BatchesCommitment"
|
||||
batchHashes := batchBatches.([]string)
|
||||
var status types.RollupStatus
|
||||
if confirmation.IsSuccessful {
|
||||
status = types.RollupCommitted
|
||||
@@ -543,15 +532,15 @@ func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
|
||||
status = types.RollupCommitFailed
|
||||
log.Warn("transaction confirmed but failed in layer1", "confirmation", confirmation)
|
||||
}
|
||||
for _, batchHash := range batchHashes {
|
||||
// @todo handle db error
|
||||
err := r.blockBatchOrm.UpdateCommitTxHashAndRollupStatus(r.ctx, batchHash, confirmation.TxHash.String(), status)
|
||||
if err != nil {
|
||||
log.Warn("UpdateCommitTxHashAndRollupStatus failed", "batch_hash", batchHash, "err", err)
|
||||
}
|
||||
// @todo handle db error
|
||||
err := r.batchOrm.UpdateCommitTxHashAndRollupStatus(r.ctx, batchHash.(string), confirmation.TxHash.String(), status)
|
||||
if err != nil {
|
||||
log.Warn("UpdateCommitTxHashAndRollupStatus failed",
|
||||
"batch hash", batchHash.(string),
|
||||
"tx hash", confirmation.TxHash.String(), "err", err)
|
||||
}
|
||||
bridgeL2BatchesCommittedConfirmedTotalCounter.Inc(int64(len(batchHashes)))
|
||||
r.processingBatchesCommitment.Delete(confirmation.ID)
|
||||
bridgeL2BatchesCommittedConfirmedTotalCounter.Inc(1)
|
||||
r.processingCommitment.Delete(confirmation.ID)
|
||||
}
|
||||
|
||||
// check whether it is proof finalization transaction
|
||||
@@ -564,10 +553,13 @@ func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
|
||||
status = types.RollupFinalizeFailed
|
||||
log.Warn("transaction confirmed but failed in layer1", "confirmation", confirmation)
|
||||
}
|
||||
|
||||
// @todo handle db error
|
||||
err := r.blockBatchOrm.UpdateFinalizeTxHashAndRollupStatus(r.ctx, batchHash.(string), confirmation.TxHash.String(), status)
|
||||
err := r.batchOrm.UpdateFinalizeTxHashAndRollupStatus(r.ctx, batchHash.(string), confirmation.TxHash.String(), status)
|
||||
if err != nil {
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed", "batch_hash", batchHash.(string), "err", err)
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed",
|
||||
"batch hash", batchHash.(string),
|
||||
"tx hash", confirmation.TxHash.String(), "err", err)
|
||||
}
|
||||
bridgeL2BatchesFinalizedConfirmedTotalCounter.Inc(1)
|
||||
r.processingFinalization.Delete(confirmation.ID)
|
||||
@@ -587,14 +579,14 @@ func (r *Layer2Relayer) handleConfirmLoop(ctx context.Context) {
|
||||
case cfm := <-r.gasOracleSender.ConfirmChan():
|
||||
if !cfm.IsSuccessful {
|
||||
// @discuss: maybe make it pending again?
|
||||
err := r.blockBatchOrm.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleFailed, cfm.TxHash.String())
|
||||
err := r.batchOrm.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleFailed, cfm.TxHash.String())
|
||||
if err != nil {
|
||||
log.Warn("UpdateL2GasOracleStatusAndOracleTxHash failed", "err", err)
|
||||
}
|
||||
log.Warn("transaction confirmed but failed in layer1", "confirmation", cfm)
|
||||
} else {
|
||||
// @todo handle db error
|
||||
err := r.blockBatchOrm.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleImported, cfm.TxHash.String())
|
||||
err := r.batchOrm.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleImported, cfm.TxHash.String())
|
||||
if err != nil {
|
||||
log.Warn("UpdateL2GasOracleStatusAndOracleTxHash failed", "err", err)
|
||||
}
|
||||
|
||||
@@ -2,22 +2,18 @@ package relayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/agiledragon/gomonkey/v2"
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
"scroll-tech/common/types/message"
|
||||
"scroll-tech/common/utils"
|
||||
|
||||
"scroll-tech/bridge/internal/controller/sender"
|
||||
@@ -27,20 +23,6 @@ import (
|
||||
bridgeUtils "scroll-tech/bridge/internal/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
templateL2Message = []orm.L2Message{
|
||||
{
|
||||
Nonce: 1,
|
||||
Height: 1,
|
||||
Sender: "0x596a746661dbed76a84556111c2872249b070e15",
|
||||
Value: "100",
|
||||
Target: "0x2c73620b223808297ea734d946813f0dd78eb8f7",
|
||||
Calldata: "testdata",
|
||||
Layer2Hash: "hash0",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func setupL2RelayerDB(t *testing.T) *gorm.DB {
|
||||
db, err := bridgeUtils.InitDB(cfg.DBConfig)
|
||||
assert.NoError(t, err)
|
||||
@@ -53,74 +35,37 @@ func setupL2RelayerDB(t *testing.T) *gorm.DB {
|
||||
func testCreateNewRelayer(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, relayer)
|
||||
}
|
||||
|
||||
func testL2RelayerProcessSaveEvents(t *testing.T) {
|
||||
func testL2RelayerProcessPendingBatches(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
l2Cfg := cfg.L2Config
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
l2MessageOrm := orm.NewL2Message(db)
|
||||
err = l2MessageOrm.SaveL2Messages(context.Background(), templateL2Message)
|
||||
l2BlockOrm := orm.NewL2Block(db)
|
||||
err = l2BlockOrm.InsertL2Blocks(context.Background(), []*bridgeTypes.WrappedBlock{wrappedBlock1, wrappedBlock2})
|
||||
assert.NoError(t, err)
|
||||
chunkOrm := orm.NewChunk(db)
|
||||
dbChunk1, err := chunkOrm.InsertChunk(context.Background(), chunk1)
|
||||
assert.NoError(t, err)
|
||||
dbChunk2, err := chunkOrm.InsertChunk(context.Background(), chunk2)
|
||||
assert.NoError(t, err)
|
||||
batchOrm := orm.NewBatch(db)
|
||||
batch, err := batchOrm.InsertBatch(context.Background(), 0, 1, dbChunk1.Hash, dbChunk2.Hash, []*bridgeTypes.Chunk{chunk1, chunk2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
traces := []*bridgeTypes.WrappedBlock{
|
||||
{
|
||||
Header: &gethTypes.Header{
|
||||
Number: big.NewInt(int64(templateL2Message[0].Height)),
|
||||
},
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
},
|
||||
{
|
||||
Header: &gethTypes.Header{
|
||||
Number: big.NewInt(int64(templateL2Message[0].Height + 1)),
|
||||
},
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
},
|
||||
}
|
||||
relayer.ProcessPendingBatches()
|
||||
|
||||
blockTraceOrm := orm.NewBlockTrace(db)
|
||||
assert.NoError(t, blockTraceOrm.InsertWrappedBlocks(traces))
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
parentBatch1 := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: common.Hash{}.Hex(),
|
||||
StateRoot: common.Hash{}.Hex(),
|
||||
}
|
||||
batchData1 := bridgeTypes.NewBatchData(parentBatch1, []*bridgeTypes.WrappedBlock{wrappedBlock1}, nil)
|
||||
batchHash := batchData1.Hash().Hex()
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
rowsAffected, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData1)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
dbTxErr = blockTraceOrm.UpdateBatchHashForL2Blocks(tx, []uint64{1}, batchHash)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
statuses, err := batchOrm.GetRollupStatusByHashList(context.Background(), []string{batch.Hash})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = blockBatchOrm.UpdateRollupStatus(context.Background(), batchHash, types.RollupFinalized)
|
||||
assert.NoError(t, err)
|
||||
|
||||
relayer.ProcessSavedEvents()
|
||||
|
||||
msg, err := l2MessageOrm.GetL2MessageByNonce(templateL2Message[0].Nonce)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.MsgSubmitted, types.MsgStatus(msg.Status))
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupCommitting, statuses[0])
|
||||
}
|
||||
|
||||
func testL2RelayerProcessCommittedBatches(t *testing.T) {
|
||||
@@ -128,44 +73,36 @@ func testL2RelayerProcessCommittedBatches(t *testing.T) {
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
l2Cfg := cfg.L2Config
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
batchOrm := orm.NewBatch(db)
|
||||
batch, err := batchOrm.InsertBatch(context.Background(), 0, 1, chunkHash1.Hex(), chunkHash2.Hex(), []*bridgeTypes.Chunk{chunk1, chunk2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
parentBatch1 := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: common.Hash{}.Hex(),
|
||||
StateRoot: common.Hash{}.Hex(),
|
||||
}
|
||||
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
batchData1 := bridgeTypes.NewBatchData(parentBatch1, []*bridgeTypes.WrappedBlock{wrappedBlock1}, nil)
|
||||
batchHash := batchData1.Hash().Hex()
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
rowsAffected, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData1)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
err = batchOrm.UpdateRollupStatus(context.Background(), batch.Hash, types.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = blockBatchOrm.UpdateRollupStatus(context.Background(), batchHash, types.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tProof := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
tInstanceCommitments := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
err = blockBatchOrm.UpdateProofByHash(context.Background(), batchHash, tProof, tInstanceCommitments, 100)
|
||||
assert.NoError(t, err)
|
||||
err = blockBatchOrm.UpdateProvingStatus(batchHash, types.ProvingTaskVerified)
|
||||
err = batchOrm.UpdateProvingStatus(context.Background(), batch.Hash, types.ProvingTaskVerified)
|
||||
assert.NoError(t, err)
|
||||
|
||||
relayer.ProcessCommittedBatches()
|
||||
|
||||
statuses, err := blockBatchOrm.GetRollupStatusByHashList([]string{batchHash})
|
||||
statuses, err := batchOrm.GetRollupStatusByHashList(context.Background(), []string{batch.Hash})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupFinalizationSkipped, statuses[0])
|
||||
|
||||
err = batchOrm.UpdateRollupStatus(context.Background(), batch.Hash, types.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
proof := &message.AggProof{
|
||||
Proof: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
|
||||
FinalPair: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
|
||||
}
|
||||
err = batchOrm.UpdateProofByHash(context.Background(), batch.Hash, proof, 100)
|
||||
assert.NoError(t, err)
|
||||
|
||||
relayer.ProcessCommittedBatches()
|
||||
statuses, err = batchOrm.GetRollupStatusByHashList(context.Background(), []string{batch.Hash})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupFinalizing, statuses[0])
|
||||
@@ -176,172 +113,100 @@ func testL2RelayerSkipBatches(t *testing.T) {
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
l2Cfg := cfg.L2Config
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
createBatch := func(rollupStatus types.RollupStatus, provingStatus types.ProvingStatus, index uint64) string {
|
||||
batchData := genBatchData(t, index)
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
rowsAffected, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
batchOrm := orm.NewBatch(db)
|
||||
createBatch := func(rollupStatus types.RollupStatus, provingStatus types.ProvingStatus) string {
|
||||
batch, err := batchOrm.InsertBatch(context.Background(), 0, 1, chunkHash1.Hex(), chunkHash2.Hex(), []*bridgeTypes.Chunk{chunk1, chunk2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
batchHash := batchData.Hash().Hex()
|
||||
err = blockBatchOrm.UpdateRollupStatus(context.Background(), batchHash, rollupStatus)
|
||||
err = batchOrm.UpdateRollupStatus(context.Background(), batch.Hash, rollupStatus)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tProof := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
tInstanceCommitments := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
err = blockBatchOrm.UpdateProofByHash(context.Background(), batchHash, tProof, tInstanceCommitments, 100)
|
||||
proof := &message.AggProof{
|
||||
Proof: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
|
||||
FinalPair: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
|
||||
}
|
||||
err = batchOrm.UpdateProofByHash(context.Background(), batch.Hash, proof, 100)
|
||||
assert.NoError(t, err)
|
||||
err = blockBatchOrm.UpdateProvingStatus(batchHash, provingStatus)
|
||||
err = batchOrm.UpdateProvingStatus(context.Background(), batch.Hash, provingStatus)
|
||||
assert.NoError(t, err)
|
||||
return batchHash
|
||||
return batch.Hash
|
||||
}
|
||||
|
||||
skipped := []string{
|
||||
createBatch(types.RollupCommitted, types.ProvingTaskSkipped, 1),
|
||||
createBatch(types.RollupCommitted, types.ProvingTaskFailed, 2),
|
||||
createBatch(types.RollupCommitted, types.ProvingTaskSkipped),
|
||||
createBatch(types.RollupCommitted, types.ProvingTaskFailed),
|
||||
}
|
||||
|
||||
notSkipped := []string{
|
||||
createBatch(types.RollupPending, types.ProvingTaskSkipped, 3),
|
||||
createBatch(types.RollupCommitting, types.ProvingTaskSkipped, 4),
|
||||
createBatch(types.RollupFinalizing, types.ProvingTaskSkipped, 5),
|
||||
createBatch(types.RollupFinalized, types.ProvingTaskSkipped, 6),
|
||||
createBatch(types.RollupPending, types.ProvingTaskFailed, 7),
|
||||
createBatch(types.RollupCommitting, types.ProvingTaskFailed, 8),
|
||||
createBatch(types.RollupFinalizing, types.ProvingTaskFailed, 9),
|
||||
createBatch(types.RollupFinalized, types.ProvingTaskFailed, 10),
|
||||
createBatch(types.RollupCommitted, types.ProvingTaskVerified, 11),
|
||||
createBatch(types.RollupPending, types.ProvingTaskSkipped),
|
||||
createBatch(types.RollupCommitting, types.ProvingTaskSkipped),
|
||||
createBatch(types.RollupFinalizing, types.ProvingTaskSkipped),
|
||||
createBatch(types.RollupFinalized, types.ProvingTaskSkipped),
|
||||
createBatch(types.RollupPending, types.ProvingTaskFailed),
|
||||
createBatch(types.RollupCommitting, types.ProvingTaskFailed),
|
||||
createBatch(types.RollupFinalizing, types.ProvingTaskFailed),
|
||||
createBatch(types.RollupFinalized, types.ProvingTaskFailed),
|
||||
createBatch(types.RollupCommitted, types.ProvingTaskVerified),
|
||||
}
|
||||
|
||||
relayer.ProcessCommittedBatches()
|
||||
|
||||
for _, id := range skipped {
|
||||
statuses, err := blockBatchOrm.GetRollupStatusByHashList([]string{id})
|
||||
statuses, err := batchOrm.GetRollupStatusByHashList(context.Background(), []string{id})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupFinalizationSkipped, statuses[0])
|
||||
}
|
||||
|
||||
for _, id := range notSkipped {
|
||||
statuses, err := blockBatchOrm.GetRollupStatusByHashList([]string{id})
|
||||
statuses, err := batchOrm.GetRollupStatusByHashList(context.Background(), []string{id})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.NotEqual(t, types.RollupFinalizationSkipped, statuses[0])
|
||||
}
|
||||
}
|
||||
|
||||
func testL2RelayerMsgConfirm(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
l2MessageOrm := orm.NewL2Message(db)
|
||||
insertL2Messages := []orm.L2Message{
|
||||
{MsgHash: "msg-1", Nonce: 0},
|
||||
{MsgHash: "msg-2", Nonce: 1},
|
||||
}
|
||||
err := l2MessageOrm.SaveL2Messages(context.Background(), insertL2Messages)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create and set up the Layer2 Relayer.
|
||||
l2Cfg := cfg.L2Config
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
l2Relayer, err := NewLayer2Relayer(ctx, l2Cli, db, l2Cfg.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Simulate message confirmations.
|
||||
l2Relayer.processingMessage.Store("msg-1", "msg-1")
|
||||
l2Relayer.messageSender.SendConfirmation(&sender.Confirmation{
|
||||
ID: "msg-1",
|
||||
IsSuccessful: true,
|
||||
})
|
||||
l2Relayer.processingMessage.Store("msg-2", "msg-2")
|
||||
l2Relayer.messageSender.SendConfirmation(&sender.Confirmation{
|
||||
ID: "msg-2",
|
||||
IsSuccessful: false,
|
||||
})
|
||||
|
||||
// Check the database for the updated status using TryTimes.
|
||||
assert.True(t, utils.TryTimes(5, func() bool {
|
||||
fields1 := map[string]interface{}{"msg_hash": "msg-1"}
|
||||
msg1, err1 := l2MessageOrm.GetL2Messages(fields1, nil, 0)
|
||||
if len(msg1) != 1 {
|
||||
return false
|
||||
}
|
||||
fields2 := map[string]interface{}{"msg_hash": "msg-2"}
|
||||
msg2, err2 := l2MessageOrm.GetL2Messages(fields2, nil, 0)
|
||||
if len(msg2) != 1 {
|
||||
return false
|
||||
}
|
||||
return err1 == nil && types.MsgStatus(msg1[0].Status) == types.MsgConfirmed &&
|
||||
err2 == nil && types.MsgStatus(msg2[0].Status) == types.MsgRelayFailed
|
||||
}))
|
||||
}
|
||||
|
||||
func testL2RelayerRollupConfirm(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
// Insert test data.
|
||||
batches := make([]*bridgeTypes.BatchData, 6)
|
||||
for i := 0; i < 6; i++ {
|
||||
batches[i] = genBatchData(t, uint64(i))
|
||||
}
|
||||
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
for _, batch := range batches {
|
||||
rowsAffected, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batch)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create and set up the Layer2 Relayer.
|
||||
l2Cfg := cfg.L2Config
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
l2Relayer, err := NewLayer2Relayer(ctx, l2Cli, db, l2Cfg.RelayerConfig)
|
||||
l2Relayer, err := NewLayer2Relayer(ctx, l2Cli, db, l2Cfg.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Simulate message confirmations.
|
||||
processingKeys := []string{"committed-1", "committed-2", "finalized-1", "finalized-2"}
|
||||
isSuccessful := []bool{true, false, true, false}
|
||||
|
||||
batchOrm := orm.NewBatch(db)
|
||||
batchHashes := make([]string, len(processingKeys))
|
||||
for i := range batchHashes {
|
||||
batch, err := batchOrm.InsertBatch(context.Background(), 0, 1, chunkHash1.Hex(), chunkHash2.Hex(), []*bridgeTypes.Chunk{chunk1, chunk2})
|
||||
assert.NoError(t, err)
|
||||
batchHashes[i] = batch.Hash
|
||||
}
|
||||
|
||||
for i, key := range processingKeys[:2] {
|
||||
batchHashes := []string{batches[i*2].Hash().Hex(), batches[i*2+1].Hash().Hex()}
|
||||
l2Relayer.processingBatchesCommitment.Store(key, batchHashes)
|
||||
l2Relayer.messageSender.SendConfirmation(&sender.Confirmation{
|
||||
l2Relayer.processingCommitment.Store(key, batchHashes[i])
|
||||
l2Relayer.rollupSender.SendConfirmation(&sender.Confirmation{
|
||||
ID: key,
|
||||
IsSuccessful: isSuccessful[i],
|
||||
TxHash: common.HexToHash("0x123456789abcdef"),
|
||||
})
|
||||
}
|
||||
|
||||
for i, key := range processingKeys[2:] {
|
||||
batchHash := batches[i+4].Hash().Hex()
|
||||
l2Relayer.processingFinalization.Store(key, batchHash)
|
||||
l2Relayer.processingFinalization.Store(key, batchHashes[i+2])
|
||||
l2Relayer.rollupSender.SendConfirmation(&sender.Confirmation{
|
||||
ID: key,
|
||||
IsSuccessful: isSuccessful[i+2],
|
||||
TxHash: common.HexToHash("0x56789abcdef1234"),
|
||||
TxHash: common.HexToHash("0x123456789abcdef"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -349,15 +214,13 @@ func testL2RelayerRollupConfirm(t *testing.T) {
|
||||
ok := utils.TryTimes(5, func() bool {
|
||||
expectedStatuses := []types.RollupStatus{
|
||||
types.RollupCommitted,
|
||||
types.RollupCommitted,
|
||||
types.RollupCommitFailed,
|
||||
types.RollupCommitFailed,
|
||||
types.RollupFinalized,
|
||||
types.RollupFinalizeFailed,
|
||||
}
|
||||
|
||||
for i, batch := range batches[:6] {
|
||||
batchInDB, err := blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batch.Hash().Hex()}, nil, 0)
|
||||
for i, batchHash := range batchHashes {
|
||||
batchInDB, err := batchOrm.GetBatches(context.Background(), map[string]interface{}{"hash": batchHash}, nil, 0)
|
||||
if err != nil || len(batchInDB) != 1 || types.RollupStatus(batchInDB[0].RollupStatus) != expectedStatuses[i] {
|
||||
return false
|
||||
}
|
||||
@@ -371,49 +234,42 @@ func testL2RelayerGasOracleConfirm(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
// Insert test data.
|
||||
batches := make([]*bridgeTypes.BatchData, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
batches[i] = genBatchData(t, uint64(i))
|
||||
}
|
||||
batchOrm := orm.NewBatch(db)
|
||||
batch1, err := batchOrm.InsertBatch(context.Background(), 0, 0, chunkHash1.Hex(), chunkHash1.Hex(), []*bridgeTypes.Chunk{chunk1})
|
||||
assert.NoError(t, err)
|
||||
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
for _, batch := range batches {
|
||||
rowsAffected, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batch)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
batch2, err := batchOrm.InsertBatch(context.Background(), 1, 1, chunkHash2.Hex(), chunkHash2.Hex(), []*bridgeTypes.Chunk{chunk2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create and set up the Layer2 Relayer.
|
||||
l2Cfg := cfg.L2Config
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
l2Relayer, err := NewLayer2Relayer(ctx, l2Cli, db, l2Cfg.RelayerConfig)
|
||||
l2Relayer, err := NewLayer2Relayer(ctx, l2Cli, db, l2Cfg.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Simulate message confirmations.
|
||||
isSuccessful := []bool{true, false}
|
||||
for i, batch := range batches {
|
||||
l2Relayer.gasOracleSender.SendConfirmation(&sender.Confirmation{
|
||||
ID: batch.Hash().Hex(),
|
||||
IsSuccessful: isSuccessful[i],
|
||||
})
|
||||
type BatchConfirmation struct {
|
||||
batchHash string
|
||||
isSuccessful bool
|
||||
}
|
||||
|
||||
confirmations := []BatchConfirmation{
|
||||
{batchHash: batch1.Hash, isSuccessful: true},
|
||||
{batchHash: batch2.Hash, isSuccessful: false},
|
||||
}
|
||||
|
||||
for _, confirmation := range confirmations {
|
||||
l2Relayer.gasOracleSender.SendConfirmation(&sender.Confirmation{
|
||||
ID: confirmation.batchHash,
|
||||
IsSuccessful: confirmation.isSuccessful,
|
||||
})
|
||||
}
|
||||
// Check the database for the updated status using TryTimes.
|
||||
ok := utils.TryTimes(5, func() bool {
|
||||
expectedStatuses := []types.GasOracleStatus{types.GasOracleImported, types.GasOracleFailed}
|
||||
for i, batch := range batches {
|
||||
gasOracle, err := blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batch.Hash().Hex()}, nil, 0)
|
||||
for i, confirmation := range confirmations {
|
||||
gasOracle, err := batchOrm.GetBatches(context.Background(), map[string]interface{}{"hash": confirmation.batchHash}, nil, 0)
|
||||
if err != nil || len(gasOracle) != 1 || types.GasOracleStatus(gasOracle[0].OracleStatus) != expectedStatuses[i] {
|
||||
return false
|
||||
}
|
||||
@@ -423,42 +279,27 @@ func testL2RelayerGasOracleConfirm(t *testing.T) {
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func genBatchData(t *testing.T, index uint64) *bridgeTypes.BatchData {
|
||||
templateBlockTrace, err := os.ReadFile("../../../testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
// unmarshal blockTrace
|
||||
wrappedBlock := &bridgeTypes.WrappedBlock{}
|
||||
err = json.Unmarshal(templateBlockTrace, wrappedBlock)
|
||||
assert.NoError(t, err)
|
||||
wrappedBlock.Header.ParentHash = common.HexToHash("0x" + strconv.FormatUint(index+1, 16))
|
||||
parentBatch := &bridgeTypes.BatchInfo{
|
||||
Index: index,
|
||||
Hash: "0x0000000000000000000000000000000000000000",
|
||||
}
|
||||
return bridgeTypes.NewBatchData(parentBatch, []*bridgeTypes.WrappedBlock{wrappedBlock}, nil)
|
||||
}
|
||||
|
||||
func testLayer2RelayerProcessGasPriceOracle(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, relayer)
|
||||
|
||||
var blockBatchOrm *orm.BlockBatch
|
||||
var batchOrm *orm.Batch
|
||||
convey.Convey("Failed to GetLatestBatch", t, func() {
|
||||
targetErr := errors.New("GetLatestBatch error")
|
||||
patchGuard := gomonkey.ApplyMethodFunc(blockBatchOrm, "GetLatestBatch", func() (*orm.BlockBatch, error) {
|
||||
patchGuard := gomonkey.ApplyMethodFunc(batchOrm, "GetLatestBatch", func(context.Context) (*orm.Batch, error) {
|
||||
return nil, targetErr
|
||||
})
|
||||
defer patchGuard.Reset()
|
||||
relayer.ProcessGasPriceOracle()
|
||||
})
|
||||
|
||||
patchGuard := gomonkey.ApplyMethodFunc(blockBatchOrm, "GetLatestBatch", func() (*orm.BlockBatch, error) {
|
||||
batch := orm.BlockBatch{
|
||||
OracleStatus: int(types.GasOraclePending),
|
||||
patchGuard := gomonkey.ApplyMethodFunc(batchOrm, "GetLatestBatch", func(context.Context) (*orm.Batch, error) {
|
||||
batch := orm.Batch{
|
||||
OracleStatus: int16(types.GasOraclePending),
|
||||
Hash: "0x0000000000000000000000000000000000000000",
|
||||
}
|
||||
return &batch, nil
|
||||
@@ -503,97 +344,14 @@ func testLayer2RelayerProcessGasPriceOracle(t *testing.T) {
|
||||
|
||||
convey.Convey("UpdateGasOracleStatusAndOracleTxHash failed", t, func() {
|
||||
targetErr := errors.New("UpdateL2GasOracleStatusAndOracleTxHash error")
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateL2GasOracleStatusAndOracleTxHash", func(ctx context.Context, hash string, status types.GasOracleStatus, txHash string) error {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "UpdateL2GasOracleStatusAndOracleTxHash", func(ctx context.Context, hash string, status types.GasOracleStatus, txHash string) error {
|
||||
return targetErr
|
||||
})
|
||||
relayer.ProcessGasPriceOracle()
|
||||
})
|
||||
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateL2GasOracleStatusAndOracleTxHash", func(ctx context.Context, hash string, status types.GasOracleStatus, txHash string) error {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "UpdateL2GasOracleStatusAndOracleTxHash", func(ctx context.Context, hash string, status types.GasOracleStatus, txHash string) error {
|
||||
return nil
|
||||
})
|
||||
relayer.ProcessGasPriceOracle()
|
||||
}
|
||||
|
||||
func testLayer2RelayerSendCommitTx(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, relayer)
|
||||
|
||||
var batchDataList []*bridgeTypes.BatchData
|
||||
convey.Convey("SendCommitTx receives empty batch", t, func() {
|
||||
err = relayer.SendCommitTx(batchDataList)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
parentBatch := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: "0x0000000000000000000000000000000000000000",
|
||||
}
|
||||
|
||||
traces := []*bridgeTypes.WrappedBlock{
|
||||
{
|
||||
Header: &gethTypes.Header{
|
||||
Number: big.NewInt(1000),
|
||||
ParentHash: common.Hash{},
|
||||
Difficulty: big.NewInt(0),
|
||||
BaseFee: big.NewInt(0),
|
||||
},
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
},
|
||||
}
|
||||
|
||||
blocks := []*bridgeTypes.WrappedBlock{traces[0]}
|
||||
tmpBatchData := bridgeTypes.NewBatchData(parentBatch, blocks, cfg.L2Config.BatchProposerConfig.PublicInputConfig)
|
||||
batchDataList = append(batchDataList, tmpBatchData)
|
||||
|
||||
var s abi.ABI
|
||||
convey.Convey("Failed to pack commitBatches", t, func() {
|
||||
targetErr := errors.New("commitBatches error")
|
||||
patchGuard := gomonkey.ApplyMethodFunc(s, "Pack", func(name string, args ...interface{}) ([]byte, error) {
|
||||
return nil, targetErr
|
||||
})
|
||||
defer patchGuard.Reset()
|
||||
|
||||
err = relayer.SendCommitTx(batchDataList)
|
||||
assert.EqualError(t, err, targetErr.Error())
|
||||
})
|
||||
|
||||
patchGuard := gomonkey.ApplyMethodFunc(s, "Pack", func(name string, args ...interface{}) ([]byte, error) {
|
||||
return nil, nil
|
||||
})
|
||||
defer patchGuard.Reset()
|
||||
|
||||
convey.Convey("Failed to send commitBatches tx to layer1", t, func() {
|
||||
targetErr := errors.New("SendTransaction failure")
|
||||
patchGuard.ApplyMethodFunc(relayer.rollupSender, "SendTransaction", func(ID string, target *common.Address, value *big.Int, data []byte, minGasLimit uint64) (hash common.Hash, err error) {
|
||||
return common.Hash{}, targetErr
|
||||
})
|
||||
err = relayer.SendCommitTx(batchDataList)
|
||||
assert.EqualError(t, err, targetErr.Error())
|
||||
})
|
||||
|
||||
patchGuard.ApplyMethodFunc(relayer.rollupSender, "SendTransaction", func(ID string, target *common.Address, value *big.Int, data []byte, minGasLimit uint64) (hash common.Hash, err error) {
|
||||
return common.HexToHash("0x56789abcdef1234"), nil
|
||||
})
|
||||
|
||||
var blockBatchOrm *orm.BlockBatch
|
||||
convey.Convey("UpdateCommitTxHashAndRollupStatus failed", t, func() {
|
||||
targetErr := errors.New("UpdateCommitTxHashAndRollupStatus failure")
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateCommitTxHashAndRollupStatus", func(ctx context.Context, hash string, commitTxHash string, status types.RollupStatus) error {
|
||||
return targetErr
|
||||
})
|
||||
err = relayer.SendCommitTx(batchDataList)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateCommitTxHashAndRollupStatus", func(ctx context.Context, hash string, commitTxHash string, status types.RollupStatus) error {
|
||||
return nil
|
||||
})
|
||||
err = relayer.SendCommitTx(batchDataList)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
@@ -24,17 +24,20 @@ var (
|
||||
// l2geth client
|
||||
l2Cli *ethclient.Client
|
||||
|
||||
// block trace
|
||||
// l2 block
|
||||
wrappedBlock1 *bridgeTypes.WrappedBlock
|
||||
wrappedBlock2 *bridgeTypes.WrappedBlock
|
||||
|
||||
// batch data
|
||||
batchData1 *bridgeTypes.BatchData
|
||||
batchData2 *bridgeTypes.BatchData
|
||||
// chunk
|
||||
chunk1 *bridgeTypes.Chunk
|
||||
chunk2 *bridgeTypes.Chunk
|
||||
chunkHash1 common.Hash
|
||||
chunkHash2 common.Hash
|
||||
)
|
||||
|
||||
func setupEnv(t *testing.T) (err error) {
|
||||
func setupEnv(t *testing.T) {
|
||||
// Load config.
|
||||
var err error
|
||||
cfg, err = config.NewConfig("../../../conf/config.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -54,40 +57,22 @@ func setupEnv(t *testing.T) (err error) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
templateBlockTrace1, err := os.ReadFile("../../../testdata/blockTrace_02.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// unmarshal blockTrace
|
||||
assert.NoError(t, err)
|
||||
wrappedBlock1 = &bridgeTypes.WrappedBlock{}
|
||||
if err = json.Unmarshal(templateBlockTrace1, wrappedBlock1); err != nil {
|
||||
return err
|
||||
}
|
||||
parentBatch1 := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: "0x0cc6b102c2924402c14b2e3a19baccc316252bfdc44d9ec62e942d34e39ec729",
|
||||
StateRoot: "0x2579122e8f9ec1e862e7d415cef2fb495d7698a8e5f0dddc5651ba4236336e7d",
|
||||
}
|
||||
batchData1 = bridgeTypes.NewBatchData(parentBatch1, []*bridgeTypes.WrappedBlock{wrappedBlock1}, nil)
|
||||
err = json.Unmarshal(templateBlockTrace1, wrappedBlock1)
|
||||
assert.NoError(t, err)
|
||||
chunk1 = &bridgeTypes.Chunk{Blocks: []*bridgeTypes.WrappedBlock{wrappedBlock1}}
|
||||
chunkHash1, err = chunk1.Hash(0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
templateBlockTrace2, err := os.ReadFile("../../../testdata/blockTrace_03.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// unmarshal blockTrace
|
||||
assert.NoError(t, err)
|
||||
wrappedBlock2 = &bridgeTypes.WrappedBlock{}
|
||||
if err = json.Unmarshal(templateBlockTrace2, wrappedBlock2); err != nil {
|
||||
return err
|
||||
}
|
||||
parentBatch2 := &bridgeTypes.BatchInfo{
|
||||
Index: batchData1.Batch.BatchIndex,
|
||||
Hash: batchData1.Hash().Hex(),
|
||||
StateRoot: batchData1.Batch.NewStateRoot.String(),
|
||||
}
|
||||
batchData2 = bridgeTypes.NewBatchData(parentBatch2, []*bridgeTypes.WrappedBlock{wrappedBlock2}, nil)
|
||||
|
||||
log.Info("batchHash", "batchhash1", batchData1.Hash().Hex(), "batchhash2", batchData2.Hash().Hex())
|
||||
|
||||
return err
|
||||
err = json.Unmarshal(templateBlockTrace2, wrappedBlock2)
|
||||
assert.NoError(t, err)
|
||||
chunk2 = &bridgeTypes.Chunk{Blocks: []*bridgeTypes.WrappedBlock{wrappedBlock2}}
|
||||
chunkHash2, err = chunk2.Hash(chunk1.NumL1Messages(0))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -99,9 +84,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestFunctions(t *testing.T) {
|
||||
if err := setupEnv(t); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setupEnv(t)
|
||||
// Run l1 relayer test cases.
|
||||
t.Run("TestCreateNewL1Relayer", testCreateNewL1Relayer)
|
||||
t.Run("TestL1RelayerProcessSaveEvents", testL1RelayerProcessSaveEvents)
|
||||
@@ -111,12 +94,10 @@ func TestFunctions(t *testing.T) {
|
||||
|
||||
// Run l2 relayer test cases.
|
||||
t.Run("TestCreateNewRelayer", testCreateNewRelayer)
|
||||
t.Run("TestL2RelayerProcessSaveEvents", testL2RelayerProcessSaveEvents)
|
||||
t.Run("TestL2RelayerProcessPendingBatches", testL2RelayerProcessPendingBatches)
|
||||
t.Run("TestL2RelayerProcessCommittedBatches", testL2RelayerProcessCommittedBatches)
|
||||
t.Run("TestL2RelayerSkipBatches", testL2RelayerSkipBatches)
|
||||
t.Run("TestL2RelayerMsgConfirm", testL2RelayerMsgConfirm)
|
||||
t.Run("TestL2RelayerRollupConfirm", testL2RelayerRollupConfirm)
|
||||
t.Run("TestL2RelayerGasOracleConfirm", testL2RelayerGasOracleConfirm)
|
||||
t.Run("TestLayer2RelayerProcessGasPriceOracle", testLayer2RelayerProcessGasPriceOracle)
|
||||
t.Run("TestLayer2RelayerSendCommitTx", testLayer2RelayerSendCommitTx)
|
||||
}
|
||||
|
||||
@@ -3,391 +3,169 @@ package watcher
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
gethMetrics "github.com/scroll-tech/go-ethereum/metrics"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/metrics"
|
||||
"scroll-tech/common/types"
|
||||
|
||||
bridgeAbi "scroll-tech/bridge/abi"
|
||||
"scroll-tech/bridge/internal/config"
|
||||
"scroll-tech/bridge/internal/controller/relayer"
|
||||
"scroll-tech/bridge/internal/orm"
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
)
|
||||
|
||||
var (
|
||||
bridgeL2BatchesGasOverThresholdTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/gas/over/threshold/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesTxsOverThresholdTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/txs/over/threshold/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesBlocksCreatedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/blocks/created/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesCommitsSentTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/commits/sent/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesOversizedTotalCounter = gethMetrics.NewRegisteredCounter("bridge/l2/batches/oversized/total", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesTxsCreatedPerBatchGauge = gethMetrics.NewRegisteredGauge("bridge/l2/batches/txs/created/per/batch", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesGasCreatedPerBatchGauge = gethMetrics.NewRegisteredGauge("bridge/l2/batches/gas/created/per/batch", metrics.ScrollRegistry)
|
||||
bridgeL2BatchesPayloadSizePerBatchGauge = gethMetrics.NewRegisteredGauge("bridge/l2/batches/payload/size/per/batch", metrics.ScrollRegistry)
|
||||
)
|
||||
|
||||
// BatchProposer sends batches commit transactions to relayer.
|
||||
// BatchProposer proposes batches based on available unbatched chunks.
|
||||
type BatchProposer struct {
|
||||
mutex sync.Mutex
|
||||
ctx context.Context
|
||||
db *gorm.DB
|
||||
ctx context.Context
|
||||
db *gorm.DB
|
||||
|
||||
batchTimeSec uint64
|
||||
batchGasThreshold uint64
|
||||
batchTxNumThreshold uint64
|
||||
batchBlocksLimit uint64
|
||||
batchCommitTimeSec uint64
|
||||
commitCalldataSizeLimit uint64
|
||||
batchDataBufferSizeLimit uint64
|
||||
commitCalldataMinSize uint64
|
||||
batchOrm *orm.Batch
|
||||
chunkOrm *orm.Chunk
|
||||
l2Block *orm.L2Block
|
||||
|
||||
proofGenerationFreq uint64
|
||||
batchDataBuffer []*bridgeTypes.BatchData
|
||||
relayer *relayer.Layer2Relayer
|
||||
|
||||
blockBatchOrm *orm.BlockBatch
|
||||
blockTraceOrm *orm.BlockTrace
|
||||
|
||||
piCfg *bridgeTypes.PublicInputHashConfig
|
||||
maxChunkNumPerBatch uint64
|
||||
maxL1CommitGasPerBatch uint64
|
||||
maxL1CommitCalldataSizePerBatch uint64
|
||||
minChunkNumPerBatch uint64
|
||||
batchTimeoutSec uint64
|
||||
}
|
||||
|
||||
// NewBatchProposer will return a new instance of BatchProposer.
|
||||
func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, relayer *relayer.Layer2Relayer, db *gorm.DB) *BatchProposer {
|
||||
p := &BatchProposer{
|
||||
mutex: sync.Mutex{},
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
blockBatchOrm: orm.NewBlockBatch(db),
|
||||
blockTraceOrm: orm.NewBlockTrace(db),
|
||||
batchTimeSec: cfg.BatchTimeSec,
|
||||
batchGasThreshold: cfg.BatchGasThreshold,
|
||||
batchTxNumThreshold: cfg.BatchTxNumThreshold,
|
||||
batchBlocksLimit: cfg.BatchBlocksLimit,
|
||||
batchCommitTimeSec: cfg.BatchCommitTimeSec,
|
||||
commitCalldataSizeLimit: cfg.CommitTxCalldataSizeLimit,
|
||||
commitCalldataMinSize: cfg.CommitTxCalldataMinSize,
|
||||
batchDataBufferSizeLimit: 100*cfg.CommitTxCalldataSizeLimit + 1*1024*1024, // @todo: determine the value.
|
||||
proofGenerationFreq: cfg.ProofGenerationFreq,
|
||||
piCfg: cfg.PublicInputConfig,
|
||||
relayer: relayer,
|
||||
}
|
||||
|
||||
// for graceful restart.
|
||||
p.recoverBatchDataBuffer()
|
||||
|
||||
// try to commit the leftover pending batches
|
||||
p.TryCommitBatches()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *BatchProposer) recoverBatchDataBuffer() {
|
||||
// batches are sorted by batch index in increasing order
|
||||
batchHashes, err := p.blockBatchOrm.GetBlockBatchesHashByRollupStatus(types.RollupPending, math.MaxInt32)
|
||||
if err != nil {
|
||||
log.Crit("Failed to fetch pending L2 batches", "err", err)
|
||||
}
|
||||
if len(batchHashes) == 0 {
|
||||
return
|
||||
}
|
||||
log.Info("Load pending batches into batchDataBuffer")
|
||||
|
||||
// helper function to cache and get BlockBatch from DB
|
||||
blockBatchCache := make(map[string]orm.BlockBatch)
|
||||
getBlockBatch := func(batchHash string) (*orm.BlockBatch, error) {
|
||||
if blockBatch, ok := blockBatchCache[batchHash]; ok {
|
||||
return &blockBatch, nil
|
||||
}
|
||||
blockBatches, err := p.blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batchHash}, nil, 0)
|
||||
if err != nil || len(blockBatches) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
blockBatchCache[batchHash] = blockBatches[0]
|
||||
return &blockBatches[0], nil
|
||||
}
|
||||
|
||||
// recover the in-memory batchData from DB
|
||||
for _, batchHash := range batchHashes {
|
||||
log.Info("recover batch data from pending batch", "batch_hash", batchHash)
|
||||
blockBatch, err := getBlockBatch(batchHash)
|
||||
if err != nil {
|
||||
log.Error("could not get BlockBatch", "batch_hash", batchHash, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
parentBatch, err := getBlockBatch(blockBatch.ParentHash)
|
||||
if err != nil {
|
||||
log.Error("could not get parent BlockBatch", "batch_hash", batchHash, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
whereFileds := map[string]interface{}{
|
||||
"batch_hash": batchHash,
|
||||
}
|
||||
orderByList := []string{
|
||||
"number ASC",
|
||||
}
|
||||
|
||||
blockTraces, err := p.blockTraceOrm.GetL2BlockInfos(whereFileds, orderByList, 0)
|
||||
if err != nil {
|
||||
log.Error("could not GetL2BlockInfos", "batch_hash", batchHash, "error", err)
|
||||
continue
|
||||
}
|
||||
if len(blockTraces) != int(blockBatch.EndBlockNumber-blockBatch.StartBlockNumber+1) {
|
||||
log.Error("the number of block info retrieved from DB mistmatches the batch info in the DB",
|
||||
"len(blockInfos)", len(blockTraces),
|
||||
"expected", blockBatch.EndBlockNumber-blockBatch.StartBlockNumber+1)
|
||||
continue
|
||||
}
|
||||
|
||||
batchData, err := p.generateBatchData(parentBatch, blockTraces)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if batchData.Hash().Hex() != batchHash {
|
||||
log.Error("the hash from recovered batch data mismatches the DB entry",
|
||||
"recovered_batch_hash", batchData.Hash().Hex(),
|
||||
"expected", batchHash)
|
||||
continue
|
||||
}
|
||||
|
||||
p.batchDataBuffer = append(p.batchDataBuffer, batchData)
|
||||
// NewBatchProposer creates a new BatchProposer instance.
|
||||
func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, db *gorm.DB) *BatchProposer {
|
||||
return &BatchProposer{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
batchOrm: orm.NewBatch(db),
|
||||
chunkOrm: orm.NewChunk(db),
|
||||
l2Block: orm.NewL2Block(db),
|
||||
maxChunkNumPerBatch: cfg.MaxChunkNumPerBatch,
|
||||
maxL1CommitGasPerBatch: cfg.MaxL1CommitGasPerBatch,
|
||||
maxL1CommitCalldataSizePerBatch: cfg.MaxL1CommitCalldataSizePerBatch,
|
||||
minChunkNumPerBatch: cfg.MinChunkNumPerBatch,
|
||||
batchTimeoutSec: cfg.BatchTimeoutSec,
|
||||
}
|
||||
}
|
||||
|
||||
// TryProposeBatch will try to propose a batch.
|
||||
// TryProposeBatch tries to propose a new batches.
|
||||
func (p *BatchProposer) TryProposeBatch() {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
for p.getBatchDataBufferSize() < p.batchDataBufferSizeLimit {
|
||||
orderBy := []string{"number ASC"}
|
||||
blockTraces, err := p.blockTraceOrm.GetUnbatchedL2Blocks(map[string]interface{}{}, orderBy, int(p.batchBlocksLimit))
|
||||
if err != nil {
|
||||
log.Error("failed to get unbatched blocks", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
batchCreated := p.proposeBatch(blockTraces)
|
||||
|
||||
// while size of batchDataBuffer < commitCalldataMinSize,
|
||||
// proposer keeps fetching and porposing batches.
|
||||
if p.getBatchDataBufferSize() >= p.commitCalldataMinSize {
|
||||
return
|
||||
}
|
||||
|
||||
if !batchCreated {
|
||||
// wait for watcher to insert l2 traces.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TryCommitBatches will try to commit the pending batches.
|
||||
func (p *BatchProposer) TryCommitBatches() {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if len(p.batchDataBuffer) == 0 {
|
||||
dbChunks, err := p.proposeBatchChunks()
|
||||
if err != nil {
|
||||
log.Error("proposeBatchChunks failed", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// estimate the calldata length to determine whether to commit the pending batches
|
||||
index := 0
|
||||
commit := false
|
||||
calldataByteLen := uint64(0)
|
||||
for ; index < len(p.batchDataBuffer); index++ {
|
||||
calldataByteLen += bridgeAbi.GetBatchCalldataLength(&p.batchDataBuffer[index].Batch)
|
||||
if calldataByteLen > p.commitCalldataSizeLimit {
|
||||
commit = true
|
||||
if index == 0 {
|
||||
log.Warn(
|
||||
"The calldata size of one batch is larger than the threshold",
|
||||
"batch_hash", p.batchDataBuffer[0].Hash().Hex(),
|
||||
"calldata_size", calldataByteLen,
|
||||
)
|
||||
index = 1
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !commit && p.batchDataBuffer[0].Timestamp()+p.batchCommitTimeSec > uint64(time.Now().Unix()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Send commit tx for batchDataBuffer[0:index]
|
||||
log.Info("Commit batches", "start_index", p.batchDataBuffer[0].Batch.BatchIndex,
|
||||
"end_index", p.batchDataBuffer[index-1].Batch.BatchIndex)
|
||||
err := p.relayer.SendCommitTx(p.batchDataBuffer[:index])
|
||||
if err != nil {
|
||||
// leave the retry to the next ticker
|
||||
log.Error("SendCommitTx failed", "error", err)
|
||||
} else {
|
||||
// pop the processed batches from the buffer
|
||||
bridgeL2BatchesCommitsSentTotalCounter.Inc(1)
|
||||
p.batchDataBuffer = p.batchDataBuffer[index:]
|
||||
if err := p.updateBatchInfoInDB(dbChunks); err != nil {
|
||||
log.Error("update batch info in db failed", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BatchProposer) proposeBatch(blockTraces []orm.BlockTrace) bool {
|
||||
if len(blockTraces) == 0 {
|
||||
return false
|
||||
func (p *BatchProposer) updateBatchInfoInDB(dbChunks []*orm.Chunk) error {
|
||||
numChunks := len(dbChunks)
|
||||
if numChunks <= 0 {
|
||||
return nil
|
||||
}
|
||||
chunks, err := p.dbChunksToBridgeChunks(dbChunks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
approximatePayloadSize := func(hash string) (uint64, error) {
|
||||
traces, err := p.blockTraceOrm.GetL2WrappedBlocks(map[string]interface{}{"hash": hash})
|
||||
startChunkIndex := dbChunks[0].Index
|
||||
startChunkHash := dbChunks[0].Hash
|
||||
endChunkIndex := dbChunks[numChunks-1].Index
|
||||
endChunkHash := dbChunks[numChunks-1].Hash
|
||||
err = p.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
batch, dbErr := p.batchOrm.InsertBatch(p.ctx, startChunkIndex, endChunkIndex, startChunkHash, endChunkHash, chunks, dbTX)
|
||||
if dbErr != nil {
|
||||
return dbErr
|
||||
}
|
||||
dbErr = p.chunkOrm.UpdateBatchHashInRange(p.ctx, startChunkIndex, endChunkIndex, batch.Hash, dbTX)
|
||||
if dbErr != nil {
|
||||
return dbErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *BatchProposer) proposeBatchChunks() ([]*orm.Chunk, error) {
|
||||
dbChunks, err := p.chunkOrm.GetUnbatchedChunks(p.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(dbChunks) == 0 {
|
||||
log.Warn("No Unbatched Chunks")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
firstChunk := dbChunks[0]
|
||||
totalL1CommitCalldataSize := firstChunk.TotalL1CommitCalldataSize
|
||||
totalL1CommitGas := firstChunk.TotalL1CommitGas
|
||||
var totalChunks uint64 = 1
|
||||
|
||||
// Check if the first chunk breaks hard limits.
|
||||
// If so, it indicates there are bugs in chunk-proposer, manual fix is needed.
|
||||
if totalL1CommitGas > p.maxL1CommitGasPerBatch {
|
||||
return nil, fmt.Errorf(
|
||||
"the first chunk exceeds l1 commit gas limit; start block number: %v, end block number: %v, commit gas: %v, max commit gas limit: %v",
|
||||
firstChunk.StartBlockNumber,
|
||||
firstChunk.EndBlockNumber,
|
||||
totalL1CommitGas,
|
||||
p.maxL1CommitGasPerBatch,
|
||||
)
|
||||
}
|
||||
|
||||
if totalL1CommitCalldataSize > p.maxL1CommitCalldataSizePerBatch {
|
||||
return nil, fmt.Errorf(
|
||||
"the first chunk exceeds l1 commit calldata size limit; start block number: %v, end block number %v, calldata size: %v, max calldata size limit: %v",
|
||||
firstChunk.StartBlockNumber,
|
||||
firstChunk.EndBlockNumber,
|
||||
totalL1CommitCalldataSize,
|
||||
p.maxL1CommitCalldataSizePerBatch,
|
||||
)
|
||||
}
|
||||
|
||||
for i, chunk := range dbChunks[1:] {
|
||||
totalChunks++
|
||||
totalL1CommitCalldataSize += chunk.TotalL1CommitCalldataSize
|
||||
totalL1CommitGas += chunk.TotalL1CommitGas
|
||||
if totalChunks > p.maxChunkNumPerBatch ||
|
||||
totalL1CommitCalldataSize > p.maxL1CommitCalldataSizePerBatch ||
|
||||
totalL1CommitGas > p.maxL1CommitGasPerBatch {
|
||||
return dbChunks[:i+1], nil
|
||||
}
|
||||
}
|
||||
|
||||
var hasChunkTimeout bool
|
||||
currentTimeSec := uint64(time.Now().Unix())
|
||||
if dbChunks[0].StartBlockTime+p.batchTimeoutSec < currentTimeSec {
|
||||
log.Warn("first block timeout",
|
||||
"start block number", dbChunks[0].StartBlockNumber,
|
||||
"first block timestamp", dbChunks[0].StartBlockTime,
|
||||
"chunk outdated time threshold", currentTimeSec,
|
||||
)
|
||||
hasChunkTimeout = true
|
||||
}
|
||||
|
||||
if !hasChunkTimeout && uint64(len(dbChunks)) < p.minChunkNumPerBatch {
|
||||
log.Warn("The payload size of the batch is less than the minimum limit",
|
||||
"chunk num", len(dbChunks), "minChunkNumPerBatch", p.minChunkNumPerBatch,
|
||||
)
|
||||
return nil, nil
|
||||
}
|
||||
return dbChunks, nil
|
||||
}
|
||||
|
||||
func (p *BatchProposer) dbChunksToBridgeChunks(dbChunks []*orm.Chunk) ([]*bridgeTypes.Chunk, error) {
|
||||
chunks := make([]*bridgeTypes.Chunk, len(dbChunks))
|
||||
for i, c := range dbChunks {
|
||||
wrappedBlocks, err := p.l2Block.GetL2BlocksInRange(p.ctx, c.StartBlockNumber, c.EndBlockNumber)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(traces) != 1 {
|
||||
return 0, fmt.Errorf("unexpected traces length, expected = 1, actual = %d", len(traces))
|
||||
}
|
||||
size := 0
|
||||
for _, tx := range traces[0].Transactions {
|
||||
size += len(tx.Data)
|
||||
}
|
||||
return uint64(size), nil
|
||||
}
|
||||
|
||||
firstSize, err := approximatePayloadSize(blockTraces[0].Hash)
|
||||
if err != nil {
|
||||
log.Error("failed to create batch", "number", blockTraces[0].Number, "err", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if firstSize > p.commitCalldataSizeLimit {
|
||||
log.Warn("oversized payload even for only 1 block", "height", blockTraces[0].Number, "size", firstSize)
|
||||
// note: we should probably fail here once we can ensure this will not happen
|
||||
if err := p.createBatchForBlocks(blockTraces[:1]); err != nil {
|
||||
log.Error("failed to create batch", "number", blockTraces[0].Number, "err", err)
|
||||
return false
|
||||
}
|
||||
bridgeL2BatchesTxsCreatedPerBatchGauge.Update(int64(blockTraces[0].TxNum))
|
||||
bridgeL2BatchesGasCreatedPerBatchGauge.Update(int64(blockTraces[0].GasUsed))
|
||||
bridgeL2BatchesPayloadSizePerBatchGauge.Update(int64(firstSize))
|
||||
bridgeL2BatchesBlocksCreatedTotalCounter.Inc(1)
|
||||
bridgeL2BatchesOversizedTotalCounter.Inc(1)
|
||||
return true
|
||||
}
|
||||
|
||||
if blockTraces[0].GasUsed > p.batchGasThreshold {
|
||||
bridgeL2BatchesGasOverThresholdTotalCounter.Inc(1)
|
||||
log.Warn("gas overflow even for only 1 block", "height", blockTraces[0].Number, "gas", blockTraces[0].GasUsed)
|
||||
if err := p.createBatchForBlocks(blockTraces[:1]); err != nil {
|
||||
log.Error("failed to create batch", "number", blockTraces[0].Number, "err", err)
|
||||
} else {
|
||||
bridgeL2BatchesTxsCreatedPerBatchGauge.Update(int64(blockTraces[0].TxNum))
|
||||
bridgeL2BatchesGasCreatedPerBatchGauge.Update(int64(blockTraces[0].GasUsed))
|
||||
bridgeL2BatchesPayloadSizePerBatchGauge.Update(int64(firstSize))
|
||||
bridgeL2BatchesBlocksCreatedTotalCounter.Inc(1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if blockTraces[0].TxNum > p.batchTxNumThreshold {
|
||||
bridgeL2BatchesTxsOverThresholdTotalCounter.Inc(1)
|
||||
log.Warn("too many txs even for only 1 block", "height", blockTraces[0].Number, "tx_num", blockTraces[0].TxNum)
|
||||
if err := p.createBatchForBlocks(blockTraces[:1]); err != nil {
|
||||
log.Error("failed to create batch", "number", blockTraces[0].Number, "err", err)
|
||||
} else {
|
||||
bridgeL2BatchesTxsCreatedPerBatchGauge.Update(int64(blockTraces[0].TxNum))
|
||||
bridgeL2BatchesGasCreatedPerBatchGauge.Update(int64(blockTraces[0].GasUsed))
|
||||
bridgeL2BatchesPayloadSizePerBatchGauge.Update(int64(firstSize))
|
||||
bridgeL2BatchesBlocksCreatedTotalCounter.Inc(1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var gasUsed, txNum, payloadSize uint64
|
||||
reachThreshold := false
|
||||
// add blocks into batch until reach batchGasThreshold
|
||||
for i, block := range blockTraces {
|
||||
size, err := approximatePayloadSize(block.Hash)
|
||||
if err != nil {
|
||||
log.Error("failed to create batch", "number", block.Number, "err", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if (gasUsed+block.GasUsed > p.batchGasThreshold) || (txNum+block.TxNum > p.batchTxNumThreshold) || (payloadSize+size > p.commitCalldataSizeLimit) {
|
||||
blockTraces = blockTraces[:i]
|
||||
reachThreshold = true
|
||||
break
|
||||
}
|
||||
gasUsed += block.GasUsed
|
||||
txNum += block.TxNum
|
||||
payloadSize += size
|
||||
}
|
||||
|
||||
// if too few gas gathered, but we don't want to halt, we then check the first block in the batch:
|
||||
// if it's not old enough we will skip proposing the batch,
|
||||
// otherwise we will still propose a batch
|
||||
if !reachThreshold && blockTraces[0].BlockTimestamp+p.batchTimeSec > uint64(time.Now().Unix()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := p.createBatchForBlocks(blockTraces); err != nil {
|
||||
log.Error("failed to create batch", "from", blockTraces[0].Number, "to", blockTraces[len(blockTraces)-1].Number, "err", err)
|
||||
} else {
|
||||
bridgeL2BatchesTxsCreatedPerBatchGauge.Update(int64(txNum))
|
||||
bridgeL2BatchesGasCreatedPerBatchGauge.Update(int64(gasUsed))
|
||||
bridgeL2BatchesPayloadSizePerBatchGauge.Update(int64(payloadSize))
|
||||
bridgeL2BatchesBlocksCreatedTotalCounter.Inc(int64(len(blockTraces)))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *BatchProposer) createBatchForBlocks(blocks []orm.BlockTrace) error {
|
||||
lastBatch, err := p.blockBatchOrm.GetLatestBatch()
|
||||
if err != nil {
|
||||
// We should not receive sql.ErrNoRows error. The DB should have the batch entry that contains the genesis block.
|
||||
return err
|
||||
}
|
||||
|
||||
batchData, err := p.generateBatchData(lastBatch, blocks)
|
||||
if err != nil {
|
||||
log.Error("createBatchData failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := orm.AddBatchInfoToDB(p.db, batchData); err != nil {
|
||||
log.Error("addBatchInfoToDB failed", "BatchHash", batchData.Hash(), "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
p.batchDataBuffer = append(p.batchDataBuffer, batchData)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *BatchProposer) generateBatchData(parentBatch *orm.BlockBatch, blocks []orm.BlockTrace) (*bridgeTypes.BatchData, error) {
|
||||
var wrappedBlocks []*bridgeTypes.WrappedBlock
|
||||
for _, block := range blocks {
|
||||
trs, err := p.blockTraceOrm.GetL2WrappedBlocks(map[string]interface{}{"hash": block.Hash})
|
||||
if err != nil || len(trs) != 1 {
|
||||
log.Error("Failed to GetBlockTraces", "hash", block.Hash, "err", err)
|
||||
log.Error("Failed to fetch wrapped blocks",
|
||||
"start number", c.StartBlockNumber, "end number", c.EndBlockNumber, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedBlocks = append(wrappedBlocks, trs[0])
|
||||
chunks[i] = &bridgeTypes.Chunk{
|
||||
Blocks: wrappedBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
parentBatchInfo := bridgeTypes.BatchInfo{
|
||||
Index: parentBatch.Index,
|
||||
Hash: parentBatch.Hash,
|
||||
StateRoot: parentBatch.StateRoot,
|
||||
}
|
||||
return bridgeTypes.NewBatchData(&parentBatchInfo, wrappedBlocks, p.piCfg), nil
|
||||
}
|
||||
|
||||
func (p *BatchProposer) getBatchDataBufferSize() (size uint64) {
|
||||
for _, batchData := range p.batchDataBuffer {
|
||||
size += bridgeAbi.GetBatchCalldataLength(&batchData.Batch)
|
||||
}
|
||||
return
|
||||
return chunks, nil
|
||||
}
|
||||
|
||||
@@ -2,205 +2,71 @@ package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/agiledragon/gomonkey/v2"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
gethTtypes "github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
|
||||
"scroll-tech/bridge/internal/config"
|
||||
"scroll-tech/bridge/internal/controller/relayer"
|
||||
"scroll-tech/bridge/internal/orm"
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
bridgeUtils "scroll-tech/bridge/internal/utils"
|
||||
"scroll-tech/bridge/internal/utils"
|
||||
)
|
||||
|
||||
func testBatchProposerProposeBatch(t *testing.T) {
|
||||
// TODO: Add unit tests that the limits are enforced correctly.
|
||||
func testBatchProposer(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
defer utils.CloseDB(db)
|
||||
|
||||
p := &BatchProposer{
|
||||
batchGasThreshold: 1000,
|
||||
batchTxNumThreshold: 10,
|
||||
batchTimeSec: 300,
|
||||
commitCalldataSizeLimit: 500,
|
||||
}
|
||||
l2BlockOrm := orm.NewL2Block(db)
|
||||
err := l2BlockOrm.InsertL2Blocks(context.Background(), []*bridgeTypes.WrappedBlock{wrappedBlock1, wrappedBlock2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var blockTrace *orm.BlockTrace
|
||||
patchGuard := gomonkey.ApplyMethodFunc(blockTrace, "GetL2WrappedBlocks", func(fields map[string]interface{}) ([]*bridgeTypes.WrappedBlock, error) {
|
||||
hash, _ := fields["hash"].(string)
|
||||
if hash == "blockWithLongData" {
|
||||
longData := strings.Repeat("0", 1000)
|
||||
return []*bridgeTypes.WrappedBlock{{
|
||||
Transactions: []*gethTtypes.TransactionData{{
|
||||
Data: longData,
|
||||
}},
|
||||
}}, nil
|
||||
}
|
||||
return []*bridgeTypes.WrappedBlock{{
|
||||
Transactions: []*gethTtypes.TransactionData{{
|
||||
Data: "short",
|
||||
}},
|
||||
}}, nil
|
||||
})
|
||||
defer patchGuard.Reset()
|
||||
patchGuard.ApplyPrivateMethod(p, "createBatchForBlocks", func(*BatchProposer, []*types.BlockInfo) error {
|
||||
return nil
|
||||
})
|
||||
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
|
||||
MaxTxGasPerChunk: 1000000000,
|
||||
MaxL2TxNumPerChunk: 10000,
|
||||
MaxL1CommitGasPerChunk: 50000000000,
|
||||
MaxL1CommitCalldataSizePerChunk: 1000000,
|
||||
MinL1CommitCalldataSizePerChunk: 0,
|
||||
ChunkTimeoutSec: 300,
|
||||
}, db)
|
||||
cp.TryProposeChunk()
|
||||
|
||||
block1 := orm.BlockTrace{Number: 1, GasUsed: 100, TxNum: 1, BlockTimestamp: uint64(time.Now().Unix()) - 200}
|
||||
block2 := orm.BlockTrace{Number: 2, GasUsed: 200, TxNum: 2, BlockTimestamp: uint64(time.Now().Unix())}
|
||||
block3 := orm.BlockTrace{Number: 3, GasUsed: 300, TxNum: 11, BlockTimestamp: uint64(time.Now().Unix())}
|
||||
block4 := orm.BlockTrace{Number: 4, GasUsed: 1001, TxNum: 3, BlockTimestamp: uint64(time.Now().Unix())}
|
||||
blockOutdated := orm.BlockTrace{Number: 1, GasUsed: 100, TxNum: 1, BlockTimestamp: uint64(time.Now().Add(-400 * time.Second).Unix())}
|
||||
blockWithLongData := orm.BlockTrace{Hash: "blockWithLongData", Number: 5, GasUsed: 500, TxNum: 1, BlockTimestamp: uint64(time.Now().Unix())}
|
||||
bp := NewBatchProposer(context.Background(), &config.BatchProposerConfig{
|
||||
MaxChunkNumPerBatch: 10,
|
||||
MaxL1CommitGasPerBatch: 50000000000,
|
||||
MaxL1CommitCalldataSizePerBatch: 1000000,
|
||||
MinChunkNumPerBatch: 1,
|
||||
BatchTimeoutSec: 300,
|
||||
}, db)
|
||||
bp.TryProposeBatch()
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
blocks []orm.BlockTrace
|
||||
expectedRes bool
|
||||
}{
|
||||
{"Empty block list", []orm.BlockTrace{}, false},
|
||||
{"Single block exceeding gas threshold", []orm.BlockTrace{block4}, true},
|
||||
{"Single block exceeding transaction number threshold", []orm.BlockTrace{block3}, true},
|
||||
{"Multiple blocks meeting thresholds", []orm.BlockTrace{block1, block2, block3}, true},
|
||||
{"Multiple blocks not meeting thresholds", []orm.BlockTrace{block1, block2}, false},
|
||||
{"Outdated and valid block", []orm.BlockTrace{blockOutdated, block2}, true},
|
||||
{"Single block with long data", []orm.BlockTrace{blockWithLongData}, true},
|
||||
}
|
||||
chunkOrm := orm.NewChunk(db)
|
||||
chunks, err := chunkOrm.GetUnbatchedChunks(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, chunks)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expectedRes, p.proposeBatch(tc.blocks), "Failed on: %s", tc.description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testBatchProposerBatchGeneration(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
subCtx, cancel := context.WithCancel(context.Background())
|
||||
defer func() {
|
||||
bridgeUtils.CloseDB(db)
|
||||
cancel()
|
||||
}()
|
||||
blockTraceOrm := orm.NewBlockTrace(db)
|
||||
// Insert traces into db.
|
||||
assert.NoError(t, blockTraceOrm.InsertWrappedBlocks([]*bridgeTypes.WrappedBlock{wrappedBlock1}))
|
||||
|
||||
l2cfg := cfg.L2Config
|
||||
wc := NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessengerAddress, l2cfg.L2MessageQueueAddress, l2cfg.WithdrawTrieRootSlot, db)
|
||||
loopToFetchEvent(subCtx, wc)
|
||||
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
batch, err := blockBatchOrm.GetLatestBatch()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a new batch.
|
||||
batchData := bridgeTypes.NewBatchData(&bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: batch.Hash,
|
||||
StateRoot: batch.StateRoot,
|
||||
}, []*bridgeTypes.WrappedBlock{wrappedBlock1}, nil)
|
||||
|
||||
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proposer := NewBatchProposer(context.Background(), &config.BatchProposerConfig{
|
||||
ProofGenerationFreq: 1,
|
||||
BatchGasThreshold: 3000000,
|
||||
BatchTxNumThreshold: 135,
|
||||
BatchTimeSec: 1,
|
||||
BatchBlocksLimit: 100,
|
||||
}, relayer, db)
|
||||
proposer.TryProposeBatch()
|
||||
|
||||
infos, err := blockTraceOrm.GetUnbatchedL2Blocks(map[string]interface{}{}, []string{"number ASC"}, 100)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(infos))
|
||||
|
||||
batches, err := blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batchData.Hash().Hex()}, nil, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(batches))
|
||||
}
|
||||
|
||||
func testBatchProposerGracefulRestart(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
defer bridgeUtils.CloseDB(db)
|
||||
|
||||
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blockTraceOrm := orm.NewBlockTrace(db)
|
||||
// Insert traces into db.
|
||||
assert.NoError(t, blockTraceOrm.InsertWrappedBlocks([]*bridgeTypes.WrappedBlock{wrappedBlock2}))
|
||||
|
||||
// Insert block batch into db.
|
||||
parentBatch1 := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: common.Hash{}.String(),
|
||||
StateRoot: common.Hash{}.String(),
|
||||
}
|
||||
batchData1 := bridgeTypes.NewBatchData(parentBatch1, []*bridgeTypes.WrappedBlock{wrappedBlock1}, nil)
|
||||
|
||||
parentBatch2 := &bridgeTypes.BatchInfo{
|
||||
Index: batchData1.Batch.BatchIndex,
|
||||
Hash: batchData1.Hash().Hex(),
|
||||
StateRoot: batchData1.Batch.NewStateRoot.String(),
|
||||
}
|
||||
batchData2 := bridgeTypes.NewBatchData(parentBatch2, []*bridgeTypes.WrappedBlock{wrappedBlock2}, nil)
|
||||
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
_, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData1)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
_, dbTxErr = blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData2)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
numbers1 := []uint64{batchData1.Batch.Blocks[0].BlockNumber}
|
||||
hash1 := batchData1.Hash().Hex()
|
||||
dbTxErr = blockTraceOrm.UpdateBatchHashForL2Blocks(tx, numbers1, hash1)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
numbers2 := []uint64{batchData2.Batch.Blocks[0].BlockNumber}
|
||||
hash2 := batchData2.Hash().Hex()
|
||||
dbTxErr = blockTraceOrm.UpdateBatchHashForL2Blocks(tx, numbers2, hash2)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
err = blockBatchOrm.UpdateRollupStatus(context.Background(), batchData1.Hash().Hex(), types.RollupFinalized)
|
||||
assert.NoError(t, err)
|
||||
batchHashes, err := blockBatchOrm.GetBlockBatchesHashByRollupStatus(types.RollupPending, math.MaxInt32)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(batchHashes))
|
||||
assert.Equal(t, batchData2.Hash().Hex(), batchHashes[0])
|
||||
// test p.recoverBatchDataBuffer().
|
||||
_ = NewBatchProposer(context.Background(), &config.BatchProposerConfig{
|
||||
ProofGenerationFreq: 1,
|
||||
BatchGasThreshold: 3000000,
|
||||
BatchTxNumThreshold: 135,
|
||||
BatchTimeSec: 1,
|
||||
BatchBlocksLimit: 100,
|
||||
}, relayer, db)
|
||||
|
||||
batchHashes, err = blockBatchOrm.GetBlockBatchesHashByRollupStatus(types.RollupPending, math.MaxInt32)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(batchHashes))
|
||||
|
||||
batches, err := blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batchData2.Hash().Hex()}, nil, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(batches))
|
||||
batchOrm := orm.NewBatch(db)
|
||||
// get all batches.
|
||||
batches, err := batchOrm.GetBatches(context.Background(), map[string]interface{}{}, []string{}, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, batches, 1)
|
||||
assert.Equal(t, uint64(0), batches[0].StartChunkIndex)
|
||||
assert.Equal(t, uint64(0), batches[0].EndChunkIndex)
|
||||
assert.Equal(t, types.RollupPending, types.RollupStatus(batches[0].RollupStatus))
|
||||
assert.Equal(t, types.ProvingTaskUnassigned, types.ProvingStatus(batches[0].ProvingStatus))
|
||||
|
||||
dbChunks, err := chunkOrm.GetChunksInRange(context.Background(), 0, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, batches, 1)
|
||||
assert.Equal(t, batches[0].Hash, dbChunks[0].BatchHash)
|
||||
assert.Equal(t, types.ProvingTaskUnassigned, types.ProvingStatus(dbChunks[0].ProvingStatus))
|
||||
|
||||
blockOrm := orm.NewL2Block(db)
|
||||
blocks, err := blockOrm.GetL2Blocks(context.Background(), map[string]interface{}{}, []string{}, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, blocks, 2)
|
||||
assert.Equal(t, dbChunks[0].Hash, blocks[0].ChunkHash)
|
||||
assert.Equal(t, dbChunks[0].Hash, blocks[1].ChunkHash)
|
||||
}
|
||||
|
||||
169
bridge/internal/controller/watcher/chunk_proposer.go
Normal file
169
bridge/internal/controller/watcher/chunk_proposer.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/bridge/internal/config"
|
||||
"scroll-tech/bridge/internal/orm"
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
)
|
||||
|
||||
// ChunkProposer proposes chunks based on available unchunked blocks.
|
||||
type ChunkProposer struct {
|
||||
ctx context.Context
|
||||
db *gorm.DB
|
||||
|
||||
chunkOrm *orm.Chunk
|
||||
l2BlockOrm *orm.L2Block
|
||||
|
||||
maxTxGasPerChunk uint64
|
||||
maxL2TxNumPerChunk uint64
|
||||
maxL1CommitGasPerChunk uint64
|
||||
maxL1CommitCalldataSizePerChunk uint64
|
||||
minL1CommitCalldataSizePerChunk uint64
|
||||
chunkTimeoutSec uint64
|
||||
}
|
||||
|
||||
// NewChunkProposer creates a new ChunkProposer instance.
|
||||
func NewChunkProposer(ctx context.Context, cfg *config.ChunkProposerConfig, db *gorm.DB) *ChunkProposer {
|
||||
return &ChunkProposer{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
chunkOrm: orm.NewChunk(db),
|
||||
l2BlockOrm: orm.NewL2Block(db),
|
||||
maxTxGasPerChunk: cfg.MaxTxGasPerChunk,
|
||||
maxL2TxNumPerChunk: cfg.MaxL2TxNumPerChunk,
|
||||
maxL1CommitGasPerChunk: cfg.MaxL1CommitGasPerChunk,
|
||||
maxL1CommitCalldataSizePerChunk: cfg.MaxL1CommitCalldataSizePerChunk,
|
||||
minL1CommitCalldataSizePerChunk: cfg.MinL1CommitCalldataSizePerChunk,
|
||||
chunkTimeoutSec: cfg.ChunkTimeoutSec,
|
||||
}
|
||||
}
|
||||
|
||||
// TryProposeChunk tries to propose a new chunk.
|
||||
func (p *ChunkProposer) TryProposeChunk() {
|
||||
proposedChunk, err := p.proposeChunk()
|
||||
if err != nil {
|
||||
log.Error("propose new chunk failed", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := p.updateChunkInfoInDB(proposedChunk); err != nil {
|
||||
log.Error("update chunk info in orm failed", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ChunkProposer) updateChunkInfoInDB(chunk *bridgeTypes.Chunk) error {
|
||||
if chunk == nil {
|
||||
log.Warn("proposed chunk is nil, cannot update in DB")
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.db.Transaction(func(dbTX *gorm.DB) error {
|
||||
dbChunk, err := p.chunkOrm.InsertChunk(p.ctx, chunk, dbTX)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.l2BlockOrm.UpdateChunkHashInRange(p.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber, dbChunk.Hash, dbTX); err != nil {
|
||||
log.Error("failed to update chunk_hash for l2_blocks", "chunk hash", chunk.Hash, "start block", 0, "end block", 0, "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *ChunkProposer) proposeChunk() (*bridgeTypes.Chunk, error) {
|
||||
blocks, err := p.l2BlockOrm.GetUnchunkedBlocks(p.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(blocks) == 0 {
|
||||
log.Warn("no un-chunked blocks")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
firstBlock := blocks[0]
|
||||
totalTxGasUsed := firstBlock.Header.GasUsed
|
||||
totalL2TxNum := firstBlock.L2TxsNum()
|
||||
totalL1CommitCalldataSize := firstBlock.EstimateL1CommitCalldataSize()
|
||||
totalL1CommitGas := firstBlock.EstimateL1CommitGas()
|
||||
|
||||
// Check if the first block breaks hard limits.
|
||||
// If so, it indicates there are bugs in sequencer, manual fix is needed.
|
||||
if totalL2TxNum > p.maxL2TxNumPerChunk {
|
||||
return nil, fmt.Errorf(
|
||||
"the first block exceeds l2 tx number limit; block number: %v, number of transactions: %v, max transaction number limit: %v",
|
||||
firstBlock.Header.Number,
|
||||
totalL2TxNum,
|
||||
p.maxL2TxNumPerChunk,
|
||||
)
|
||||
}
|
||||
|
||||
if totalTxGasUsed > p.maxTxGasPerChunk {
|
||||
return nil, fmt.Errorf(
|
||||
"the first block exceeds l2 tx gas limit; block number: %v, gas used: %v, max gas limit: %v",
|
||||
firstBlock.Header.Number,
|
||||
totalTxGasUsed,
|
||||
p.maxTxGasPerChunk,
|
||||
)
|
||||
}
|
||||
|
||||
if totalL1CommitGas > p.maxL1CommitGasPerChunk {
|
||||
return nil, fmt.Errorf(
|
||||
"the first block exceeds l1 commit gas limit; block number: %v, commit gas: %v, max commit gas limit: %v",
|
||||
firstBlock.Header.Number,
|
||||
totalL1CommitGas,
|
||||
p.maxL1CommitGasPerChunk,
|
||||
)
|
||||
}
|
||||
|
||||
if totalL1CommitCalldataSize > p.maxL1CommitCalldataSizePerChunk {
|
||||
return nil, fmt.Errorf(
|
||||
"the first block exceeds l1 commit calldata size limit; block number: %v, calldata size: %v, max calldata size limit: %v",
|
||||
firstBlock.Header.Number,
|
||||
totalL1CommitCalldataSize,
|
||||
p.maxL1CommitCalldataSizePerChunk,
|
||||
)
|
||||
}
|
||||
|
||||
for i, block := range blocks[1:] {
|
||||
totalTxGasUsed += block.Header.GasUsed
|
||||
totalL2TxNum += block.L2TxsNum()
|
||||
totalL1CommitCalldataSize += block.EstimateL1CommitCalldataSize()
|
||||
totalL1CommitGas += block.EstimateL1CommitGas()
|
||||
if totalTxGasUsed > p.maxTxGasPerChunk ||
|
||||
totalL2TxNum > p.maxL2TxNumPerChunk ||
|
||||
totalL1CommitCalldataSize > p.maxL1CommitCalldataSizePerChunk ||
|
||||
totalL1CommitGas > p.maxL1CommitGasPerChunk {
|
||||
blocks = blocks[:i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var hasBlockTimeout bool
|
||||
currentTimeSec := uint64(time.Now().Unix())
|
||||
if blocks[0].Header.Time+p.chunkTimeoutSec < currentTimeSec {
|
||||
log.Warn("first block timeout",
|
||||
"block number", blocks[0].Header.Number,
|
||||
"block timestamp", blocks[0].Header.Time,
|
||||
"block outdated time threshold", currentTimeSec,
|
||||
)
|
||||
hasBlockTimeout = true
|
||||
}
|
||||
|
||||
if !hasBlockTimeout && totalL1CommitCalldataSize < p.minL1CommitCalldataSizePerChunk {
|
||||
log.Warn("The calldata size of the chunk is less than the minimum limit",
|
||||
"totalL1CommitCalldataSize", totalL1CommitCalldataSize,
|
||||
"minL1CommitCalldataSizePerChunk", p.minL1CommitCalldataSizePerChunk,
|
||||
)
|
||||
return nil, nil
|
||||
}
|
||||
return &bridgeTypes.Chunk{Blocks: blocks}, nil
|
||||
}
|
||||
45
bridge/internal/controller/watcher/chunk_proposer_test.go
Normal file
45
bridge/internal/controller/watcher/chunk_proposer_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/bridge/internal/config"
|
||||
"scroll-tech/bridge/internal/orm"
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
"scroll-tech/bridge/internal/utils"
|
||||
)
|
||||
|
||||
// TODO: Add unit tests that the limits are enforced correctly.
|
||||
func testChunkProposer(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
defer utils.CloseDB(db)
|
||||
|
||||
l2BlockOrm := orm.NewL2Block(db)
|
||||
err := l2BlockOrm.InsertL2Blocks(context.Background(), []*bridgeTypes.WrappedBlock{wrappedBlock1, wrappedBlock2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
|
||||
MaxTxGasPerChunk: 1000000000,
|
||||
MaxL2TxNumPerChunk: 10000,
|
||||
MaxL1CommitGasPerChunk: 50000000000,
|
||||
MaxL1CommitCalldataSizePerChunk: 1000000,
|
||||
MinL1CommitCalldataSizePerChunk: 0,
|
||||
ChunkTimeoutSec: 300,
|
||||
}, db)
|
||||
cp.TryProposeChunk()
|
||||
|
||||
expectedChunk := &bridgeTypes.Chunk{
|
||||
Blocks: []*bridgeTypes.WrappedBlock{wrappedBlock1, wrappedBlock2},
|
||||
}
|
||||
expectedHash, err := expectedChunk.Hash(0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
chunkOrm := orm.NewChunk(db)
|
||||
chunks, err := chunkOrm.GetUnbatchedChunks(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, chunks, 1)
|
||||
assert.Equal(t, expectedHash.Hex(), chunks[0].Hash)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ type L1WatcherClient struct {
|
||||
l1MessageOrm *orm.L1Message
|
||||
l2MessageOrm *orm.L2Message
|
||||
l1BlockOrm *orm.L1Block
|
||||
l1BatchOrm *orm.BlockBatch
|
||||
batchOrm *orm.Batch
|
||||
|
||||
// The number of new blocks to wait for a block to be confirmed
|
||||
confirmations rpc.BlockNumber
|
||||
@@ -90,7 +90,7 @@ func NewL1WatcherClient(ctx context.Context, client *ethclient.Client, startHeig
|
||||
client: client,
|
||||
l1MessageOrm: l1MessageOrm,
|
||||
l1BlockOrm: l1BlockOrm,
|
||||
l1BatchOrm: orm.NewBlockBatch(db),
|
||||
batchOrm: orm.NewBatch(db),
|
||||
l2MessageOrm: orm.NewL2Message(db),
|
||||
confirmations: confirmations,
|
||||
|
||||
@@ -245,7 +245,7 @@ func (w *L1WatcherClient) FetchContractEvent() error {
|
||||
for _, event := range rollupEvents {
|
||||
batchHashes = append(batchHashes, event.batchHash.String())
|
||||
}
|
||||
statuses, err := w.l1BatchOrm.GetRollupStatusByHashList(batchHashes)
|
||||
statuses, err := w.batchOrm.GetRollupStatusByHashList(w.ctx, batchHashes)
|
||||
if err != nil {
|
||||
log.Error("Failed to GetRollupStatusByHashList", "err", err)
|
||||
return err
|
||||
@@ -261,9 +261,9 @@ func (w *L1WatcherClient) FetchContractEvent() error {
|
||||
// only update when db status is before event status
|
||||
if event.status > status {
|
||||
if event.status == types.RollupFinalized {
|
||||
err = w.l1BatchOrm.UpdateFinalizeTxHashAndRollupStatus(w.ctx, batchHash, event.txHash.String(), event.status)
|
||||
err = w.batchOrm.UpdateFinalizeTxHashAndRollupStatus(w.ctx, batchHash, event.txHash.String(), event.status)
|
||||
} else if event.status == types.RollupCommitted {
|
||||
err = w.l1BatchOrm.UpdateCommitTxHashAndRollupStatus(w.ctx, batchHash, event.txHash.String(), event.status)
|
||||
err = w.batchOrm.UpdateCommitTxHashAndRollupStatus(w.ctx, batchHash, event.txHash.String(), event.status)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("Failed to update Rollup/Finalize TxHash and Status", "err", err)
|
||||
@@ -317,7 +317,7 @@ func (w *L1WatcherClient) parseBridgeEventLogs(logs []gethTypes.Log) ([]*orm.L1M
|
||||
msgHash := common.BytesToHash(crypto.Keccak256(event.Data))
|
||||
|
||||
l1Messages = append(l1Messages, &orm.L1Message{
|
||||
QueueIndex: event.QueueIndex.Uint64(),
|
||||
QueueIndex: event.QueueIndex,
|
||||
MsgHash: msgHash.String(),
|
||||
Height: vLog.BlockNumber,
|
||||
Sender: event.Sender.String(),
|
||||
|
||||
@@ -163,7 +163,7 @@ func testL1WatcherClientFetchContractEvent(t *testing.T) {
|
||||
return nil, nil, nil, targetErr
|
||||
})
|
||||
err := watcher.FetchContractEvent()
|
||||
assert.Equal(t, err.Error(), targetErr.Error())
|
||||
assert.EqualError(t, err, targetErr.Error())
|
||||
})
|
||||
|
||||
patchGuard.ApplyPrivateMethod(watcher, "parseBridgeEventLogs", func(*L1WatcherClient, []types.Log) ([]*orm.L1Message, []relayedMessage, []rollupEvent, error) {
|
||||
@@ -195,10 +195,10 @@ func testL1WatcherClientFetchContractEvent(t *testing.T) {
|
||||
return nil, relayedMessageEvents, rollupEvents, nil
|
||||
})
|
||||
|
||||
var blockBatchOrm *orm.BlockBatch
|
||||
var batchOrm *orm.Batch
|
||||
convey.Convey("db get rollup status by hash list failure", t, func() {
|
||||
targetErr := errors.New("get db failure")
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "GetRollupStatusByHashList", func(hashes []string) ([]commonTypes.RollupStatus, error) {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "GetRollupStatusByHashList", func(context.Context, []string) ([]commonTypes.RollupStatus, error) {
|
||||
return nil, targetErr
|
||||
})
|
||||
err := watcher.FetchContractEvent()
|
||||
@@ -206,7 +206,7 @@ func testL1WatcherClientFetchContractEvent(t *testing.T) {
|
||||
})
|
||||
|
||||
convey.Convey("rollup status mismatch batch hashes length", t, func() {
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "GetRollupStatusByHashList", func(hashes []string) ([]commonTypes.RollupStatus, error) {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "GetRollupStatusByHashList", func(context.Context, []string) ([]commonTypes.RollupStatus, error) {
|
||||
s := []commonTypes.RollupStatus{
|
||||
commonTypes.RollupFinalized,
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func testL1WatcherClientFetchContractEvent(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "GetRollupStatusByHashList", func(hashes []string) ([]commonTypes.RollupStatus, error) {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "GetRollupStatusByHashList", func(context.Context, []string) ([]commonTypes.RollupStatus, error) {
|
||||
s := []commonTypes.RollupStatus{
|
||||
commonTypes.RollupPending,
|
||||
commonTypes.RollupCommitting,
|
||||
@@ -226,27 +226,27 @@ func testL1WatcherClientFetchContractEvent(t *testing.T) {
|
||||
|
||||
convey.Convey("db update RollupFinalized status failure", t, func() {
|
||||
targetErr := errors.New("UpdateFinalizeTxHashAndRollupStatus RollupFinalized failure")
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateFinalizeTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "UpdateFinalizeTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
return targetErr
|
||||
})
|
||||
err := watcher.FetchContractEvent()
|
||||
assert.Equal(t, targetErr.Error(), err.Error())
|
||||
})
|
||||
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateFinalizeTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "UpdateFinalizeTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
convey.Convey("db update RollupCommitted status failure", t, func() {
|
||||
targetErr := errors.New("UpdateCommitTxHashAndRollupStatus RollupCommitted failure")
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateCommitTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "UpdateCommitTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
return targetErr
|
||||
})
|
||||
err := watcher.FetchContractEvent()
|
||||
assert.Equal(t, targetErr.Error(), err.Error())
|
||||
})
|
||||
|
||||
patchGuard.ApplyMethodFunc(blockBatchOrm, "UpdateCommitTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
patchGuard.ApplyMethodFunc(batchOrm, "UpdateCommitTxHashAndRollupStatus", func(context.Context, string, string, commonTypes.RollupStatus) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -313,7 +313,7 @@ func testParseBridgeEventLogsL1QueueTransactionEventSignature(t *testing.T) {
|
||||
convey.Convey("L1QueueTransactionEventSignature success", t, func() {
|
||||
patchGuard := gomonkey.ApplyFunc(utils.UnpackLog, func(c *abi.ABI, out interface{}, event string, log types.Log) error {
|
||||
tmpOut := out.(*bridgeAbi.L1QueueTransactionEvent)
|
||||
tmpOut.QueueIndex = big.NewInt(100)
|
||||
tmpOut.QueueIndex = 100
|
||||
tmpOut.Data = []byte("test data")
|
||||
tmpOut.Sender = common.HexToAddress("0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30")
|
||||
tmpOut.Value = big.NewInt(1000)
|
||||
|
||||
@@ -44,11 +44,9 @@ type L2WatcherClient struct {
|
||||
|
||||
*ethclient.Client
|
||||
|
||||
db *gorm.DB
|
||||
blockBatchOrm *orm.BlockBatch
|
||||
blockTraceOrm *orm.BlockTrace
|
||||
l1MessageOrm *orm.L1Message
|
||||
l2MessageOrm *orm.L2Message
|
||||
l2BlockOrm *orm.L2Block
|
||||
l1MessageOrm *orm.L1Message
|
||||
l2MessageOrm *orm.L2Message
|
||||
|
||||
confirmations rpc.BlockNumber
|
||||
|
||||
@@ -76,11 +74,9 @@ func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmat
|
||||
|
||||
w := L2WatcherClient{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
Client: client,
|
||||
|
||||
blockBatchOrm: orm.NewBlockBatch(db),
|
||||
blockTraceOrm: orm.NewBlockTrace(db),
|
||||
l2BlockOrm: orm.NewL2Block(db),
|
||||
l1MessageOrm: orm.NewL1Message(db),
|
||||
l2MessageOrm: l2MessageOrm,
|
||||
processedMsgHeight: uint64(savedHeight),
|
||||
@@ -96,84 +92,28 @@ func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmat
|
||||
stopped: 0,
|
||||
}
|
||||
|
||||
// Initialize genesis before we do anything else
|
||||
if err := w.initializeGenesis(); err != nil {
|
||||
panic(fmt.Sprintf("failed to initialize L2 genesis batch, err: %v", err))
|
||||
}
|
||||
|
||||
return &w
|
||||
}
|
||||
|
||||
func (w *L2WatcherClient) initializeGenesis() error {
|
||||
if count, err := w.blockBatchOrm.GetBatchCount(); err != nil {
|
||||
return fmt.Errorf("failed to get batch count: %v", err)
|
||||
} else if count > 0 {
|
||||
log.Info("genesis already imported")
|
||||
return nil
|
||||
}
|
||||
|
||||
genesis, err := w.HeaderByNumber(w.ctx, big.NewInt(0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve L2 genesis header: %v", err)
|
||||
}
|
||||
|
||||
log.Info("retrieved L2 genesis header", "hash", genesis.Hash().String())
|
||||
|
||||
blockTrace := &bridgeTypes.WrappedBlock{
|
||||
Header: genesis,
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
}
|
||||
batchData := bridgeTypes.NewGenesisBatchData(blockTrace)
|
||||
|
||||
if err = orm.AddBatchInfoToDB(w.db, batchData); err != nil {
|
||||
log.Error("failed to add batch info to DB", "BatchHash", batchData.Hash(), "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
batchHash := batchData.Hash().Hex()
|
||||
|
||||
if err = w.blockBatchOrm.UpdateProvingStatus(batchHash, types.ProvingTaskProved); err != nil {
|
||||
return fmt.Errorf("failed to update genesis batch proving status: %v", err)
|
||||
}
|
||||
|
||||
if err = w.blockBatchOrm.UpdateRollupStatus(w.ctx, batchHash, types.RollupFinalized); err != nil {
|
||||
return fmt.Errorf("failed to update genesis batch rollup status: %v", err)
|
||||
}
|
||||
|
||||
log.Info("successfully imported genesis batch")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const blockTracesFetchLimit = uint64(10)
|
||||
|
||||
// TryFetchRunningMissingBlocks try fetch missing blocks if inconsistent
|
||||
func (w *L2WatcherClient) TryFetchRunningMissingBlocks(ctx context.Context, blockHeight uint64) {
|
||||
// Get newest block in DB. must have blocks at that time.
|
||||
// Don't use "block_trace" table "trace" column's BlockTrace.Number,
|
||||
// because it might be empty if the corresponding rollup_result is finalized/finalization_skipped
|
||||
heightInDB, err := w.blockTraceOrm.GetL2BlocksLatestHeight()
|
||||
// TryFetchRunningMissingBlocks attempts to fetch and store block traces for any missing blocks.
|
||||
func (w *L2WatcherClient) TryFetchRunningMissingBlocks(blockHeight uint64) {
|
||||
heightInDB, err := w.l2BlockOrm.GetL2BlocksLatestHeight(w.ctx)
|
||||
if err != nil {
|
||||
log.Error("failed to GetL2BlocksLatestHeight", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Can't get trace from genesis block, so the default start number is 1.
|
||||
var from = uint64(1)
|
||||
if heightInDB > 0 {
|
||||
from = uint64(heightInDB) + 1
|
||||
}
|
||||
|
||||
for ; from <= blockHeight; from += blockTracesFetchLimit {
|
||||
// Fetch and store block traces for missing blocks
|
||||
for from := uint64(heightInDB) + 1; from <= blockHeight; from += blockTracesFetchLimit {
|
||||
to := from + blockTracesFetchLimit - 1
|
||||
|
||||
if to > blockHeight {
|
||||
to = blockHeight
|
||||
}
|
||||
|
||||
// Get block traces and insert into db.
|
||||
if err = w.getAndStoreBlockTraces(ctx, from, to); err != nil {
|
||||
if err = w.getAndStoreBlockTraces(w.ctx, from, to); err != nil {
|
||||
log.Error("fail to getAndStoreBlockTraces", "from", from, "to", to, "err", err)
|
||||
return
|
||||
}
|
||||
@@ -229,7 +169,7 @@ func (w *L2WatcherClient) getAndStoreBlockTraces(ctx context.Context, from, to u
|
||||
}
|
||||
|
||||
if len(blocks) > 0 {
|
||||
if err := w.blockTraceOrm.InsertWrappedBlocks(blocks); err != nil {
|
||||
if err := w.l2BlockOrm.InsertL2Blocks(w.ctx, blocks); err != nil {
|
||||
return fmt.Errorf("failed to batch insert BlockTraces: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,15 +210,15 @@ func testFetchRunningMissingBlocks(t *testing.T) {
|
||||
address, err := bind.WaitDeployed(context.Background(), l2Cli, tx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blockTraceOrm := orm.NewBlockTrace(db)
|
||||
l2BlockOrm := orm.NewL2Block(db)
|
||||
ok := cutils.TryTimes(10, func() bool {
|
||||
latestHeight, err := l2Cli.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
wc := prepareWatcherClient(l2Cli, db, address)
|
||||
wc.TryFetchRunningMissingBlocks(context.Background(), latestHeight)
|
||||
fetchedHeight, err := blockTraceOrm.GetL2BlocksLatestHeight()
|
||||
wc.TryFetchRunningMissingBlocks(latestHeight)
|
||||
fetchedHeight, err := l2BlockOrm.GetL2BlocksLatestHeight(context.Background())
|
||||
return err == nil && uint64(fetchedHeight) == latestHeight
|
||||
})
|
||||
assert.True(t, ok)
|
||||
@@ -236,6 +236,7 @@ func prepareAuth(t *testing.T, l2Cli *ethclient.Client, privateKey *ecdsa.Privat
|
||||
assert.NoError(t, err)
|
||||
auth.GasPrice, err = l2Cli.SuggestGasPrice(context.Background())
|
||||
assert.NoError(t, err)
|
||||
auth.GasLimit = 500000
|
||||
return auth
|
||||
}
|
||||
|
||||
|
||||
@@ -115,8 +115,9 @@ func TestFunction(t *testing.T) {
|
||||
t.Run("TestParseBridgeEventLogsL2FailedRelayedMessageEventSignature", testParseBridgeEventLogsL2FailedRelayedMessageEventSignature)
|
||||
t.Run("TestParseBridgeEventLogsL2AppendMessageEventSignature", testParseBridgeEventLogsL2AppendMessageEventSignature)
|
||||
|
||||
// Run batch proposer test cases.
|
||||
t.Run("TestBatchProposerProposeBatch", testBatchProposerProposeBatch)
|
||||
t.Run("TestBatchProposerBatchGeneration", testBatchProposerBatchGeneration)
|
||||
t.Run("TestBatchProposerGracefulRestart", testBatchProposerGracefulRestart)
|
||||
// Run chunk-proposer test cases.
|
||||
t.Run("TestChunkProposer", testChunkProposer)
|
||||
|
||||
// Run batch-proposer test cases.
|
||||
t.Run("TestBatchProposer", testBatchProposer)
|
||||
}
|
||||
|
||||
378
bridge/internal/orm/batch.go
Normal file
378
bridge/internal/orm/batch.go
Normal file
@@ -0,0 +1,378 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
"scroll-tech/common/types/message"
|
||||
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const defaultBatchHeaderVersion = 0
|
||||
|
||||
// Batch represents a batch of chunks.
|
||||
type Batch struct {
|
||||
db *gorm.DB `gorm:"column:-"`
|
||||
|
||||
// batch
|
||||
Index uint64 `json:"index" gorm:"column:index"`
|
||||
Hash string `json:"hash" gorm:"column:hash"`
|
||||
StartChunkIndex uint64 `json:"start_chunk_index" gorm:"column:start_chunk_index"`
|
||||
StartChunkHash string `json:"start_chunk_hash" gorm:"column:start_chunk_hash"`
|
||||
EndChunkIndex uint64 `json:"end_chunk_index" gorm:"column:end_chunk_index"`
|
||||
EndChunkHash string `json:"end_chunk_hash" gorm:"column:end_chunk_hash"`
|
||||
StateRoot string `json:"state_root" gorm:"column:state_root"`
|
||||
WithdrawRoot string `json:"withdraw_root" gorm:"column:withdraw_root"`
|
||||
BatchHeader []byte `json:"batch_header" gorm:"column:batch_header"`
|
||||
|
||||
// proof
|
||||
ProvingStatus int16 `json:"proving_status" gorm:"column:proving_status;default:1"`
|
||||
Proof []byte `json:"proof" gorm:"column:proof;default:NULL"`
|
||||
ProverAssignedAt *time.Time `json:"prover_assigned_at" gorm:"column:prover_assigned_at;default:NULL"`
|
||||
ProvedAt *time.Time `json:"proved_at" gorm:"column:proved_at;default:NULL"`
|
||||
ProofTimeSec int `json:"proof_time_sec" gorm:"column:proof_time_sec;default:NULL"`
|
||||
|
||||
// rollup
|
||||
RollupStatus int16 `json:"rollup_status" gorm:"column:rollup_status;default:1"`
|
||||
CommitTxHash string `json:"commit_tx_hash" gorm:"column:commit_tx_hash;default:NULL"`
|
||||
CommittedAt *time.Time `json:"committed_at" gorm:"column:committed_at;default:NULL"`
|
||||
FinalizeTxHash string `json:"finalize_tx_hash" gorm:"column:finalize_tx_hash;default:NULL"`
|
||||
FinalizedAt *time.Time `json:"finalized_at" gorm:"column:finalized_at;default:NULL"`
|
||||
|
||||
// gas oracle
|
||||
OracleStatus int16 `json:"oracle_status" gorm:"column:oracle_status;default:1"`
|
||||
OracleTxHash string `json:"oracle_tx_hash" gorm:"column:oracle_tx_hash;default:NULL"`
|
||||
|
||||
// metadata
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"column:deleted_at;default:NULL"`
|
||||
}
|
||||
|
||||
// NewBatch creates a new Batch database instance.
|
||||
func NewBatch(db *gorm.DB) *Batch {
|
||||
return &Batch{db: db}
|
||||
}
|
||||
|
||||
// TableName returns the table name for the Batch model.
|
||||
func (*Batch) TableName() string {
|
||||
return "batch"
|
||||
}
|
||||
|
||||
// GetBatches retrieves selected batches from the database.
|
||||
// The returned batches are sorted in ascending order by their index.
|
||||
func (o *Batch) GetBatches(ctx context.Context, fields map[string]interface{}, orderByList []string, limit int) ([]*Batch, error) {
|
||||
db := o.db.WithContext(ctx)
|
||||
|
||||
for key, value := range fields {
|
||||
db = db.Where(key, value)
|
||||
}
|
||||
|
||||
for _, orderBy := range orderByList {
|
||||
db = db.Order(orderBy)
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
db = db.Limit(limit)
|
||||
}
|
||||
|
||||
db = db.Order("index ASC")
|
||||
|
||||
var batches []*Batch
|
||||
if err := db.Find(&batches).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return batches, nil
|
||||
}
|
||||
|
||||
// GetBatchCount retrieves the total number of batches in the database.
|
||||
func (o *Batch) GetBatchCount(ctx context.Context) (uint64, error) {
|
||||
var count int64
|
||||
err := o.db.WithContext(ctx).Model(&Batch{}).Count(&count).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(count), nil
|
||||
}
|
||||
|
||||
// GetVerifiedProofByHash retrieves the verified aggregate proof for a batch with the given hash.
|
||||
func (o *Batch) GetVerifiedProofByHash(ctx context.Context, hash string) (*message.AggProof, error) {
|
||||
var batch Batch
|
||||
err := o.db.WithContext(ctx).Where("hash = ? AND proving_status = ?", hash, types.ProvingTaskVerified).First(&batch).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var proof message.AggProof
|
||||
err = json.Unmarshal(batch.Proof, &proof)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proof, nil
|
||||
}
|
||||
|
||||
// GetLatestBatch retrieves the latest batch from the database.
|
||||
func (o *Batch) GetLatestBatch(ctx context.Context) (*Batch, error) {
|
||||
var latestBatch Batch
|
||||
err := o.db.WithContext(ctx).Order("index desc").First(&latestBatch).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &latestBatch, nil
|
||||
}
|
||||
|
||||
// GetRollupStatusByHashList retrieves the rollup statuses for a list of batch hashes.
|
||||
func (o *Batch) GetRollupStatusByHashList(ctx context.Context, hashes []string) ([]types.RollupStatus, error) {
|
||||
if len(hashes) == 0 {
|
||||
return []types.RollupStatus{}, nil
|
||||
}
|
||||
|
||||
var batches []Batch
|
||||
err := o.db.WithContext(ctx).Where("hash IN ?", hashes).Find(&batches).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hashToStatusMap := make(map[string]types.RollupStatus)
|
||||
for _, batch := range batches {
|
||||
hashToStatusMap[batch.Hash] = types.RollupStatus(batch.RollupStatus)
|
||||
}
|
||||
|
||||
var statuses []types.RollupStatus
|
||||
for _, hash := range hashes {
|
||||
status, ok := hashToStatusMap[hash]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hash not found in database: %s", hash)
|
||||
}
|
||||
statuses = append(statuses, status)
|
||||
}
|
||||
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
// GetPendingBatches retrieves pending batches up to the specified limit.
|
||||
// The returned batches are sorted in ascending order by their index.
|
||||
func (o *Batch) GetPendingBatches(ctx context.Context, limit int) ([]*Batch, error) {
|
||||
if limit <= 0 {
|
||||
return nil, errors.New("limit must be greater than zero")
|
||||
}
|
||||
|
||||
var batches []*Batch
|
||||
db := o.db.WithContext(ctx)
|
||||
|
||||
db = db.Where("rollup_status = ?", types.RollupPending).Order("index ASC").Limit(limit)
|
||||
|
||||
if err := db.Find(&batches).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return batches, nil
|
||||
}
|
||||
|
||||
// GetBatchByIndex retrieves the batch by the given index.
|
||||
func (o *Batch) GetBatchByIndex(ctx context.Context, index uint64) (*Batch, error) {
|
||||
var batch Batch
|
||||
err := o.db.WithContext(ctx).Where("index = ?", index).First(&batch).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &batch, nil
|
||||
}
|
||||
|
||||
// InsertBatch inserts a new batch into the database.
|
||||
func (o *Batch) InsertBatch(ctx context.Context, startChunkIndex, endChunkIndex uint64, startChunkHash, endChunkHash string, chunks []*bridgeTypes.Chunk, dbTX ...*gorm.DB) (*Batch, error) {
|
||||
if len(chunks) == 0 {
|
||||
return nil, errors.New("invalid args")
|
||||
}
|
||||
|
||||
db := o.db
|
||||
if len(dbTX) > 0 && dbTX[0] != nil {
|
||||
db = dbTX[0]
|
||||
}
|
||||
|
||||
parentBatch, err := o.GetLatestBatch(ctx)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error("failed to get the latest batch", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var batchIndex uint64
|
||||
var parentBatchHash common.Hash
|
||||
var totalL1MessagePoppedBefore uint64
|
||||
var version uint8 = defaultBatchHeaderVersion
|
||||
|
||||
// if parentBatch==nil then err==gorm.ErrRecordNotFound, which means there's
|
||||
// not batch record in the db, we then use default empty values for the creating batch;
|
||||
// if parentBatch!=nil then err=nil, then we fill the parentBatch-related data into the creating batch
|
||||
if parentBatch != nil {
|
||||
batchIndex = parentBatch.Index + 1
|
||||
parentBatchHash = common.HexToHash(parentBatch.Hash)
|
||||
|
||||
var parentBatchHeader *bridgeTypes.BatchHeader
|
||||
parentBatchHeader, err = bridgeTypes.DecodeBatchHeader(parentBatch.BatchHeader)
|
||||
if err != nil {
|
||||
log.Error("failed to decode parent batch header", "index", parentBatch.Index, "hash", parentBatch.Hash, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalL1MessagePoppedBefore = parentBatchHeader.TotalL1MessagePopped()
|
||||
version = parentBatchHeader.Version()
|
||||
}
|
||||
|
||||
batchHeader, err := bridgeTypes.NewBatchHeader(version, batchIndex, totalL1MessagePoppedBefore, parentBatchHash, chunks)
|
||||
if err != nil {
|
||||
log.Error("failed to create batch header",
|
||||
"index", batchIndex, "total l1 message popped before", totalL1MessagePoppedBefore,
|
||||
"parent hash", parentBatchHash, "number of chunks", len(chunks), "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numChunks := len(chunks)
|
||||
lastChunkBlockNum := len(chunks[numChunks-1].Blocks)
|
||||
|
||||
newBatch := Batch{
|
||||
Index: batchIndex,
|
||||
Hash: batchHeader.Hash().Hex(),
|
||||
StartChunkHash: startChunkHash,
|
||||
StartChunkIndex: startChunkIndex,
|
||||
EndChunkHash: endChunkHash,
|
||||
EndChunkIndex: endChunkIndex,
|
||||
StateRoot: chunks[numChunks-1].Blocks[lastChunkBlockNum-1].Header.Root.Hex(),
|
||||
WithdrawRoot: chunks[numChunks-1].Blocks[lastChunkBlockNum-1].WithdrawTrieRoot.Hex(),
|
||||
BatchHeader: batchHeader.Encode(),
|
||||
ProvingStatus: int16(types.ProvingTaskUnassigned),
|
||||
RollupStatus: int16(types.RollupPending),
|
||||
}
|
||||
|
||||
if err := db.WithContext(ctx).Create(&newBatch).Error; err != nil {
|
||||
log.Error("failed to insert batch", "batch", newBatch, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &newBatch, nil
|
||||
}
|
||||
|
||||
// UpdateSkippedBatches updates the skipped batches in the database.
|
||||
func (o *Batch) UpdateSkippedBatches(ctx context.Context) (uint64, error) {
|
||||
provingStatusList := []interface{}{
|
||||
int(types.ProvingTaskSkipped),
|
||||
int(types.ProvingTaskFailed),
|
||||
}
|
||||
result := o.db.Model(&Batch{}).Where("rollup_status", int(types.RollupCommitted)).
|
||||
Where("proving_status IN (?)", provingStatusList).Update("rollup_status", int(types.RollupFinalizationSkipped))
|
||||
if result.Error != nil {
|
||||
return 0, result.Error
|
||||
}
|
||||
return uint64(result.RowsAffected), nil
|
||||
}
|
||||
|
||||
// UpdateL2GasOracleStatusAndOracleTxHash updates the L2 gas oracle status and transaction hash for a batch.
|
||||
func (o *Batch) UpdateL2GasOracleStatusAndOracleTxHash(ctx context.Context, hash string, status types.GasOracleStatus, txHash string) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["oracle_status"] = int(status)
|
||||
updateFields["oracle_tx_hash"] = txHash
|
||||
if err := o.db.WithContext(ctx).Model(&Batch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProvingStatus updates the proving status of a batch.
|
||||
func (o *Batch) UpdateProvingStatus(ctx context.Context, hash string, status types.ProvingStatus, dbTX ...*gorm.DB) error {
|
||||
db := o.db
|
||||
if len(dbTX) > 0 && dbTX[0] != nil {
|
||||
db = dbTX[0]
|
||||
}
|
||||
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["proving_status"] = int(status)
|
||||
|
||||
switch status {
|
||||
case types.ProvingTaskAssigned:
|
||||
updateFields["prover_assigned_at"] = time.Now()
|
||||
case types.ProvingTaskUnassigned:
|
||||
updateFields["prover_assigned_at"] = nil
|
||||
case types.ProvingTaskProved, types.ProvingTaskVerified:
|
||||
updateFields["proved_at"] = time.Now()
|
||||
default:
|
||||
}
|
||||
|
||||
if err := db.Model(&Batch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRollupStatus updates the rollup status of a batch.
|
||||
func (o *Batch) UpdateRollupStatus(ctx context.Context, hash string, status types.RollupStatus, dbTX ...*gorm.DB) error {
|
||||
db := o.db
|
||||
if len(dbTX) > 0 && dbTX[0] != nil {
|
||||
db = dbTX[0]
|
||||
}
|
||||
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["rollup_status"] = int(status)
|
||||
|
||||
switch status {
|
||||
case types.RollupCommitted:
|
||||
updateFields["committed_at"] = time.Now()
|
||||
case types.RollupFinalized:
|
||||
updateFields["finalized_at"] = time.Now()
|
||||
}
|
||||
if err := db.Model(&Batch{}).WithContext(ctx).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCommitTxHashAndRollupStatus updates the commit transaction hash and rollup status for a batch.
|
||||
func (o *Batch) UpdateCommitTxHashAndRollupStatus(ctx context.Context, hash string, commitTxHash string, status types.RollupStatus) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["commit_tx_hash"] = commitTxHash
|
||||
updateFields["rollup_status"] = int(status)
|
||||
if status == types.RollupCommitted {
|
||||
updateFields["committed_at"] = time.Now()
|
||||
}
|
||||
if err := o.db.WithContext(ctx).Model(&Batch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateFinalizeTxHashAndRollupStatus updates the finalize transaction hash and rollup status for a batch.
|
||||
func (o *Batch) UpdateFinalizeTxHashAndRollupStatus(ctx context.Context, hash string, finalizeTxHash string, status types.RollupStatus) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["finalize_tx_hash"] = finalizeTxHash
|
||||
updateFields["rollup_status"] = int(status)
|
||||
if status == types.RollupFinalized {
|
||||
updateFields["finalized_at"] = time.Now()
|
||||
}
|
||||
if err := o.db.WithContext(ctx).Model(&Batch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProofByHash updates the block batch proof by hash.
|
||||
// for unit test.
|
||||
func (o *Batch) UpdateProofByHash(ctx context.Context, hash string, proof *message.AggProof, proofTimeSec uint64) error {
|
||||
proofBytes, err := json.Marshal(proof)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["proof"] = proofBytes
|
||||
updateFields["proof_time_sec"] = proofTimeSec
|
||||
err = o.db.WithContext(ctx).Model(&Batch{}).Where("hash", hash).Updates(updateFields).Error
|
||||
return err
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
)
|
||||
|
||||
// BlockBatch is structure of stored block batch message
|
||||
type BlockBatch struct {
|
||||
db *gorm.DB `gorm:"column:-"`
|
||||
|
||||
Hash string `json:"hash" gorm:"column:hash"`
|
||||
Index uint64 `json:"index" gorm:"column:index"`
|
||||
StartBlockNumber uint64 `json:"start_block_number" gorm:"column:start_block_number"`
|
||||
StartBlockHash string `json:"start_block_hash" gorm:"column:start_block_hash"`
|
||||
EndBlockNumber uint64 `json:"end_block_number" gorm:"column:end_block_number"`
|
||||
EndBlockHash string `json:"end_block_hash" gorm:"column:end_block_hash"`
|
||||
ParentHash string `json:"parent_hash" gorm:"column:parent_hash"`
|
||||
StateRoot string `json:"state_root" gorm:"column:state_root"`
|
||||
TotalTxNum uint64 `json:"total_tx_num" gorm:"column:total_tx_num"`
|
||||
TotalL1TxNum uint64 `json:"total_l1_tx_num" gorm:"column:total_l1_tx_num"`
|
||||
TotalL2Gas uint64 `json:"total_l2_gas" gorm:"column:total_l2_gas"`
|
||||
ProvingStatus int `json:"proving_status" gorm:"column:proving_status;default:1"`
|
||||
Proof []byte `json:"proof" gorm:"column:proof"`
|
||||
InstanceCommitments []byte `json:"instance_commitments" gorm:"column:instance_commitments"`
|
||||
ProofTimeSec uint64 `json:"proof_time_sec" gorm:"column:proof_time_sec;default:0"`
|
||||
RollupStatus int `json:"rollup_status" gorm:"column:rollup_status;default:1"`
|
||||
CommitTxHash string `json:"commit_tx_hash" gorm:"column:commit_tx_hash;default:NULL"`
|
||||
OracleStatus int `json:"oracle_status" gorm:"column:oracle_status;default:1"`
|
||||
OracleTxHash string `json:"oracle_tx_hash" gorm:"column:oracle_tx_hash;default:NULL"`
|
||||
FinalizeTxHash string `json:"finalize_tx_hash" gorm:"column:finalize_tx_hash;default:NULL"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at;default:CURRENT_TIMESTAMP()"`
|
||||
ProverAssignedAt *time.Time `json:"prover_assigned_at" gorm:"column:prover_assigned_at;default:NULL"`
|
||||
ProvedAt *time.Time `json:"proved_at" gorm:"column:proved_at;default:NULL"`
|
||||
CommittedAt *time.Time `json:"committed_at" gorm:"column:committed_at;default:NULL"`
|
||||
FinalizedAt *time.Time `json:"finalized_at" gorm:"column:finalized_at;default:NULL"`
|
||||
}
|
||||
|
||||
// NewBlockBatch create an blockBatchOrm instance
|
||||
func NewBlockBatch(db *gorm.DB) *BlockBatch {
|
||||
return &BlockBatch{db: db}
|
||||
}
|
||||
|
||||
// TableName define the BlockBatch table name
|
||||
func (*BlockBatch) TableName() string {
|
||||
return "block_batch"
|
||||
}
|
||||
|
||||
// GetBatchCount get the batch count
|
||||
func (o *BlockBatch) GetBatchCount() (int64, error) {
|
||||
var count int64
|
||||
if err := o.db.Model(&BlockBatch{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// GetBlockBatches get the select block batches
|
||||
func (o *BlockBatch) GetBlockBatches(fields map[string]interface{}, orderByList []string, limit int) ([]BlockBatch, error) {
|
||||
var blockBatches []BlockBatch
|
||||
db := o.db
|
||||
for key, value := range fields {
|
||||
db = db.Where(key, value)
|
||||
}
|
||||
|
||||
for _, orderBy := range orderByList {
|
||||
db = db.Order(orderBy)
|
||||
}
|
||||
|
||||
if limit != 0 {
|
||||
db = db.Limit(limit)
|
||||
}
|
||||
|
||||
if err := db.Find(&blockBatches).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blockBatches, nil
|
||||
}
|
||||
|
||||
// GetBlockBatchesHashByRollupStatus get the block batches by rollup status
|
||||
func (o *BlockBatch) GetBlockBatchesHashByRollupStatus(status types.RollupStatus, limit int) ([]string, error) {
|
||||
var blockBatches []BlockBatch
|
||||
err := o.db.Select("hash").Where("rollup_status", int(status)).Order("index ASC").Limit(limit).Find(&blockBatches).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hashes []string
|
||||
for _, v := range blockBatches {
|
||||
hashes = append(hashes, v.Hash)
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// GetVerifiedProofAndInstanceCommitmentsByHash get verified proof and instance comments by hash
|
||||
func (o *BlockBatch) GetVerifiedProofAndInstanceCommitmentsByHash(hash string) ([]byte, []byte, error) {
|
||||
var blockBatch BlockBatch
|
||||
err := o.db.Select("proof, instance_commitments").Where("hash", hash).Where("proving_status", int(types.ProvingTaskVerified)).Find(&blockBatch).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return blockBatch.Proof, blockBatch.InstanceCommitments, nil
|
||||
}
|
||||
|
||||
// GetLatestBatch get the latest batch
|
||||
// because we will `initializeGenesis()` when we start the `L2Watcher`, so a batch must exist.
|
||||
func (o *BlockBatch) GetLatestBatch() (*BlockBatch, error) {
|
||||
var blockBatch BlockBatch
|
||||
err := o.db.Order("index DESC").Limit(1).First(&blockBatch).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
return &blockBatch, nil
|
||||
}
|
||||
|
||||
// GetLatestBatchByRollupStatus get the latest block batch by rollup status
|
||||
func (o *BlockBatch) GetLatestBatchByRollupStatus(rollupStatuses []types.RollupStatus) (*BlockBatch, error) {
|
||||
var tmpRollupStatus []int
|
||||
for _, v := range rollupStatuses {
|
||||
tmpRollupStatus = append(tmpRollupStatus, int(v))
|
||||
}
|
||||
var blockBatch BlockBatch
|
||||
err := o.db.Where("rollup_status IN (?)", tmpRollupStatus).Order("index DESC").Limit(1).First(&blockBatch).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &blockBatch, nil
|
||||
}
|
||||
|
||||
// GetRollupStatusByHashList get rollup status by hash list
|
||||
func (o *BlockBatch) GetRollupStatusByHashList(hashes []string) ([]types.RollupStatus, error) {
|
||||
if len(hashes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var blockBatches []BlockBatch
|
||||
err := o.db.Select("hash, rollup_status").Where("hash IN (?)", hashes).Find(&blockBatches).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var statuses []types.RollupStatus
|
||||
for _, v := range blockBatches {
|
||||
statuses = append(statuses, types.RollupStatus(v.RollupStatus))
|
||||
}
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
// InsertBlockBatchByBatchData insert a block batch data by the BatchData
|
||||
func (o *BlockBatch) InsertBlockBatchByBatchData(tx *gorm.DB, batchData *bridgeTypes.BatchData) (int64, error) {
|
||||
var db *gorm.DB
|
||||
if tx != nil {
|
||||
db = tx
|
||||
} else {
|
||||
db = o.db
|
||||
}
|
||||
|
||||
numBlocks := len(batchData.Batch.Blocks)
|
||||
insertBlockBatch := BlockBatch{
|
||||
Hash: batchData.Hash().Hex(),
|
||||
Index: batchData.Batch.BatchIndex,
|
||||
StartBlockNumber: batchData.Batch.Blocks[0].BlockNumber,
|
||||
StartBlockHash: batchData.Batch.Blocks[0].BlockHash.Hex(),
|
||||
EndBlockNumber: batchData.Batch.Blocks[numBlocks-1].BlockNumber,
|
||||
EndBlockHash: batchData.Batch.Blocks[numBlocks-1].BlockHash.Hex(),
|
||||
ParentHash: batchData.Batch.ParentBatchHash.Hex(),
|
||||
StateRoot: batchData.Batch.NewStateRoot.Hex(),
|
||||
TotalTxNum: batchData.TotalTxNum,
|
||||
TotalL1TxNum: batchData.TotalL1TxNum,
|
||||
TotalL2Gas: batchData.TotalL2Gas,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
result := db.Create(&insertBlockBatch)
|
||||
if result.Error != nil {
|
||||
log.Error("failed to insert block batch by batchData", "err", result.Error)
|
||||
return 0, result.Error
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
}
|
||||
|
||||
// UpdateProvingStatus update the proving status
|
||||
func (o *BlockBatch) UpdateProvingStatus(hash string, status types.ProvingStatus) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["proving_status"] = int(status)
|
||||
|
||||
switch status {
|
||||
case types.ProvingTaskAssigned:
|
||||
updateFields["prover_assigned_at"] = time.Now()
|
||||
case types.ProvingTaskUnassigned:
|
||||
updateFields["prover_assigned_at"] = nil
|
||||
case types.ProvingTaskProved, types.ProvingTaskVerified:
|
||||
updateFields["proved_at"] = time.Now()
|
||||
default:
|
||||
}
|
||||
|
||||
if err := o.db.Model(&BlockBatch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRollupStatus update the rollup status
|
||||
func (o *BlockBatch) UpdateRollupStatus(ctx context.Context, hash string, status types.RollupStatus) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["rollup_status"] = int(status)
|
||||
|
||||
switch status {
|
||||
case types.RollupCommitted:
|
||||
updateFields["committed_at"] = time.Now()
|
||||
case types.RollupFinalized:
|
||||
updateFields["finalized_at"] = time.Now()
|
||||
}
|
||||
if err := o.db.Model(&BlockBatch{}).WithContext(ctx).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSkippedBatches update the skipped batches
|
||||
func (o *BlockBatch) UpdateSkippedBatches() (int64, error) {
|
||||
provingStatusList := []interface{}{
|
||||
int(types.ProvingTaskSkipped),
|
||||
int(types.ProvingTaskFailed),
|
||||
}
|
||||
result := o.db.Model(&BlockBatch{}).Where("rollup_status", int(types.RollupCommitted)).
|
||||
Where("proving_status IN (?)", provingStatusList).Update("rollup_status", int(types.RollupFinalizationSkipped))
|
||||
if result.Error != nil {
|
||||
return 0, result.Error
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
}
|
||||
|
||||
// UpdateCommitTxHashAndRollupStatus update the commit tx hash and rollup status
|
||||
func (o *BlockBatch) UpdateCommitTxHashAndRollupStatus(ctx context.Context, hash string, commitTxHash string, status types.RollupStatus) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["commit_tx_hash"] = commitTxHash
|
||||
updateFields["rollup_status"] = int(status)
|
||||
if status == types.RollupCommitted {
|
||||
updateFields["committed_at"] = time.Now()
|
||||
}
|
||||
if err := o.db.WithContext(ctx).Model(&BlockBatch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateFinalizeTxHashAndRollupStatus update the finalize tx hash and rollup status
|
||||
func (o *BlockBatch) UpdateFinalizeTxHashAndRollupStatus(ctx context.Context, hash string, finalizeTxHash string, status types.RollupStatus) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["finalize_tx_hash"] = finalizeTxHash
|
||||
updateFields["rollup_status"] = int(status)
|
||||
if status == types.RollupFinalized {
|
||||
updateFields["finalized_at"] = time.Now()
|
||||
}
|
||||
if err := o.db.WithContext(ctx).Model(&BlockBatch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateL2GasOracleStatusAndOracleTxHash update the l2 gas oracle status and oracle tx hash
|
||||
func (o *BlockBatch) UpdateL2GasOracleStatusAndOracleTxHash(ctx context.Context, hash string, status types.GasOracleStatus, txHash string) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["oracle_status"] = int(status)
|
||||
updateFields["oracle_tx_hash"] = txHash
|
||||
if err := o.db.WithContext(ctx).Model(&BlockBatch{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProofByHash update the block batch proof by hash
|
||||
// for unit test
|
||||
func (o *BlockBatch) UpdateProofByHash(ctx context.Context, hash string, proof, instanceCommitments []byte, proofTimeSec uint64) error {
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["proof"] = proof
|
||||
updateFields["instance_commitments"] = instanceCommitments
|
||||
updateFields["proof_time_sec"] = proofTimeSec
|
||||
err := o.db.WithContext(ctx).Model(&BlockBatch{}).Where("hash", hash).Updates(updateFields).Error
|
||||
if err != nil {
|
||||
log.Error("failed to update proof", "err", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/bridge/internal/types"
|
||||
)
|
||||
|
||||
// BlockTrace is structure of stored block trace message
|
||||
type BlockTrace struct {
|
||||
db *gorm.DB `gorm:"column:-"`
|
||||
|
||||
Number uint64 `json:"number" gorm:"number"`
|
||||
Hash string `json:"hash" gorm:"hash"`
|
||||
ParentHash string `json:"parent_hash" gorm:"parent_hash"`
|
||||
Trace string `json:"trace" gorm:"column:trace"`
|
||||
BatchHash string `json:"batch_hash" gorm:"batch_hash;default:NULL"`
|
||||
TxNum uint64 `json:"tx_num" gorm:"tx_num"`
|
||||
GasUsed uint64 `json:"gas_used" gorm:"gas_used"`
|
||||
BlockTimestamp uint64 `json:"block_timestamp" gorm:"block_timestamp"`
|
||||
}
|
||||
|
||||
// NewBlockTrace create an blockTraceOrm instance
|
||||
func NewBlockTrace(db *gorm.DB) *BlockTrace {
|
||||
return &BlockTrace{db: db}
|
||||
}
|
||||
|
||||
// TableName define the BlockTrace table name
|
||||
func (*BlockTrace) TableName() string {
|
||||
return "block_trace"
|
||||
}
|
||||
|
||||
// GetL2BlocksLatestHeight get the l2 blocks latest height
|
||||
func (o *BlockTrace) GetL2BlocksLatestHeight() (int64, error) {
|
||||
result := o.db.Model(&BlockTrace{}).Select("COALESCE(MAX(number), -1)").Row()
|
||||
if result.Err() != nil {
|
||||
return -1, result.Err()
|
||||
}
|
||||
var maxNumber int64
|
||||
if err := result.Scan(&maxNumber); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return maxNumber, nil
|
||||
}
|
||||
|
||||
// GetL2WrappedBlocks get the l2 wrapped blocks
|
||||
func (o *BlockTrace) GetL2WrappedBlocks(fields map[string]interface{}) ([]*types.WrappedBlock, error) {
|
||||
var blockTraces []BlockTrace
|
||||
db := o.db.Select("trace")
|
||||
for key, value := range fields {
|
||||
db = db.Where(key, value)
|
||||
}
|
||||
if err := db.Find(&blockTraces).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wrappedBlocks []*types.WrappedBlock
|
||||
for _, v := range blockTraces {
|
||||
var wrappedBlock types.WrappedBlock
|
||||
if err := json.Unmarshal([]byte(v.Trace), &wrappedBlock); err != nil {
|
||||
break
|
||||
}
|
||||
wrappedBlocks = append(wrappedBlocks, &wrappedBlock)
|
||||
}
|
||||
return wrappedBlocks, nil
|
||||
}
|
||||
|
||||
// GetL2BlockInfos get l2 block infos
|
||||
func (o *BlockTrace) GetL2BlockInfos(fields map[string]interface{}, orderByList []string, limit int) ([]BlockTrace, error) {
|
||||
var blockTraces []BlockTrace
|
||||
db := o.db.Select("number, hash, parent_hash, batch_hash, tx_num, gas_used, block_timestamp")
|
||||
for key, value := range fields {
|
||||
db = db.Where(key, value)
|
||||
}
|
||||
|
||||
for _, orderBy := range orderByList {
|
||||
db = db.Order(orderBy)
|
||||
}
|
||||
|
||||
if limit != 0 {
|
||||
db = db.Limit(limit)
|
||||
}
|
||||
|
||||
if err := db.Find(&blockTraces).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blockTraces, nil
|
||||
}
|
||||
|
||||
// GetUnbatchedL2Blocks get unbatched l2 blocks
|
||||
func (o *BlockTrace) GetUnbatchedL2Blocks(fields map[string]interface{}, orderByList []string, limit int) ([]BlockTrace, error) {
|
||||
var unbatchedBlockTraces []BlockTrace
|
||||
db := o.db.Select("number, hash, parent_hash, batch_hash, tx_num, gas_used, block_timestamp").Where("batch_hash is NULL")
|
||||
for key, value := range fields {
|
||||
db = db.Where(key, value)
|
||||
}
|
||||
if err := db.Find(&unbatchedBlockTraces).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return unbatchedBlockTraces, nil
|
||||
}
|
||||
|
||||
// InsertWrappedBlocks insert block to block trace
|
||||
func (o *BlockTrace) InsertWrappedBlocks(blocks []*types.WrappedBlock) error {
|
||||
var blockTraces []BlockTrace
|
||||
for _, block := range blocks {
|
||||
number := block.Header.Number.Uint64()
|
||||
hash := block.Header.Hash().String()
|
||||
txNum := len(block.Transactions)
|
||||
mtime := block.Header.Time
|
||||
gasCost := block.Header.GasUsed
|
||||
|
||||
data, err := json.Marshal(block)
|
||||
if err != nil {
|
||||
log.Error("failed to marshal block", "hash", hash, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tmpBlockTrace := BlockTrace{
|
||||
Number: number,
|
||||
Hash: hash,
|
||||
ParentHash: block.Header.ParentHash.String(),
|
||||
Trace: string(data),
|
||||
TxNum: uint64(txNum),
|
||||
GasUsed: gasCost,
|
||||
BlockTimestamp: mtime,
|
||||
}
|
||||
blockTraces = append(blockTraces, tmpBlockTrace)
|
||||
}
|
||||
|
||||
if err := o.db.Create(&blockTraces).Error; err != nil {
|
||||
log.Error("failed to insert blockTraces", "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateBatchHashForL2Blocks update the batch_hash of block trace
|
||||
func (o *BlockTrace) UpdateBatchHashForL2Blocks(tx *gorm.DB, numbers []uint64, batchHash string) error {
|
||||
var db *gorm.DB
|
||||
if tx != nil {
|
||||
db = tx
|
||||
} else {
|
||||
db = o.db
|
||||
}
|
||||
|
||||
err := db.Model(&BlockTrace{}).Where("number IN (?)", numbers).Update("batch_hash", batchHash).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
218
bridge/internal/orm/chunk.go
Normal file
218
bridge/internal/orm/chunk.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Chunk represents a chunk of blocks in the database.
|
||||
type Chunk struct {
|
||||
db *gorm.DB `gorm:"-"`
|
||||
|
||||
// chunk
|
||||
Index uint64 `json:"index" gorm:"column:index"`
|
||||
Hash string `json:"hash" gorm:"column:hash"`
|
||||
StartBlockNumber uint64 `json:"start_block_number" gorm:"column:start_block_number"`
|
||||
StartBlockHash string `json:"start_block_hash" gorm:"column:start_block_hash"`
|
||||
EndBlockNumber uint64 `json:"end_block_number" gorm:"column:end_block_number"`
|
||||
EndBlockHash string `json:"end_block_hash" gorm:"column:end_block_hash"`
|
||||
StartBlockTime uint64 `json:"start_block_time" gorm:"column:start_block_time"`
|
||||
TotalL1MessagesPoppedBefore uint64 `json:"total_l1_messages_popped_before" gorm:"column:total_l1_messages_popped_before"`
|
||||
TotalL1MessagesPoppedInChunk uint64 `json:"total_l1_messages_popped_in_chunk" gorm:"column:total_l1_messages_popped_in_chunk"`
|
||||
|
||||
// proof
|
||||
ProvingStatus int16 `json:"proving_status" gorm:"column:proving_status;default:1"`
|
||||
Proof []byte `json:"proof" gorm:"column:proof;default:NULL"`
|
||||
ProverAssignedAt *time.Time `json:"prover_assigned_at" gorm:"column:prover_assigned_at;default:NULL"`
|
||||
ProvedAt *time.Time `json:"proved_at" gorm:"column:proved_at;default:NULL"`
|
||||
ProofTimeSec int16 `json:"proof_time_sec" gorm:"column:proof_time_sec;default:NULL"`
|
||||
|
||||
// batch
|
||||
BatchHash string `json:"batch_hash" gorm:"column:batch_hash;default:NULL"`
|
||||
|
||||
// metadata
|
||||
TotalL2TxGas uint64 `json:"total_l2_tx_gas" gorm:"column:total_l2_tx_gas"`
|
||||
TotalL2TxNum uint64 `json:"total_l2_tx_num" gorm:"column:total_l2_tx_num"`
|
||||
TotalL1CommitCalldataSize uint64 `json:"total_l1_commit_calldata_size" gorm:"column:total_l1_commit_calldata_size"`
|
||||
TotalL1CommitGas uint64 `json:"total_l1_commit_gas" gorm:"column:total_l1_commit_gas"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"column:deleted_at;default:NULL"`
|
||||
}
|
||||
|
||||
// NewChunk creates a new Chunk database instance.
|
||||
func NewChunk(db *gorm.DB) *Chunk {
|
||||
return &Chunk{db: db}
|
||||
}
|
||||
|
||||
// TableName returns the table name for the chunk model.
|
||||
func (*Chunk) TableName() string {
|
||||
return "chunk"
|
||||
}
|
||||
|
||||
// GetChunksInRange retrieves chunks within a given range (inclusive) from the database.
|
||||
// The range is closed, i.e., it includes both start and end indices.
|
||||
// The returned chunks are sorted in ascending order by their index.
|
||||
func (o *Chunk) GetChunksInRange(ctx context.Context, startIndex uint64, endIndex uint64) ([]*Chunk, error) {
|
||||
if startIndex > endIndex {
|
||||
return nil, errors.New("start index should be less than or equal to end index")
|
||||
}
|
||||
|
||||
var chunks []*Chunk
|
||||
db := o.db.WithContext(ctx).Where("index >= ? AND index <= ?", startIndex, endIndex)
|
||||
db = db.Order("index ASC")
|
||||
|
||||
if err := db.Find(&chunks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if startIndex+uint64(len(chunks)) != endIndex+1 {
|
||||
return nil, errors.New("number of chunks not expected in the specified range")
|
||||
}
|
||||
|
||||
return chunks, nil
|
||||
}
|
||||
|
||||
// GetUnbatchedChunks retrieves unbatched chunks from the database.
|
||||
func (o *Chunk) GetUnbatchedChunks(ctx context.Context) ([]*Chunk, error) {
|
||||
var chunks []*Chunk
|
||||
err := o.db.WithContext(ctx).
|
||||
Where("batch_hash IS NULL").
|
||||
Order("index asc").
|
||||
Find(&chunks).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chunks, nil
|
||||
}
|
||||
|
||||
// GetLatestChunk retrieves the latest chunk from the database.
|
||||
func (o *Chunk) GetLatestChunk(ctx context.Context) (*Chunk, error) {
|
||||
var latestChunk Chunk
|
||||
err := o.db.WithContext(ctx).
|
||||
Order("index desc").
|
||||
First(&latestChunk).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &latestChunk, nil
|
||||
}
|
||||
|
||||
// InsertChunk inserts a new chunk into the database.
|
||||
func (o *Chunk) InsertChunk(ctx context.Context, chunk *bridgeTypes.Chunk, dbTX ...*gorm.DB) (*Chunk, error) {
|
||||
if chunk == nil || len(chunk.Blocks) == 0 {
|
||||
return nil, errors.New("invalid args")
|
||||
}
|
||||
|
||||
db := o.db
|
||||
if len(dbTX) > 0 && dbTX[0] != nil {
|
||||
db = dbTX[0]
|
||||
}
|
||||
|
||||
var chunkIndex uint64
|
||||
var totalL1MessagePoppedBefore uint64
|
||||
parentChunk, err := o.GetLatestChunk(ctx)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error("failed to get latest chunk", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if parentChunk==nil then err==gorm.ErrRecordNotFound, which means there's
|
||||
// not chunk record in the db, we then use default empty values for the creating chunk;
|
||||
// if parentChunk!=nil then err=nil, then we fill the parentChunk-related data into the creating chunk
|
||||
if parentChunk != nil {
|
||||
chunkIndex = parentChunk.Index + 1
|
||||
totalL1MessagePoppedBefore = parentChunk.TotalL1MessagesPoppedBefore + parentChunk.TotalL1MessagesPoppedInChunk
|
||||
}
|
||||
|
||||
hash, err := chunk.Hash(totalL1MessagePoppedBefore)
|
||||
if err != nil {
|
||||
log.Error("failed to get chunk hash", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var totalL2TxGas uint64
|
||||
var totalL2TxNum uint64
|
||||
var totalL1CommitCalldataSize uint64
|
||||
var totalL1CommitGas uint64
|
||||
for _, block := range chunk.Blocks {
|
||||
totalL2TxGas += block.Header.GasUsed
|
||||
totalL2TxNum += block.L2TxsNum()
|
||||
totalL1CommitCalldataSize += block.EstimateL1CommitCalldataSize()
|
||||
totalL1CommitGas += block.EstimateL1CommitGas()
|
||||
}
|
||||
|
||||
numBlocks := len(chunk.Blocks)
|
||||
newChunk := Chunk{
|
||||
Index: chunkIndex,
|
||||
Hash: hash.Hex(),
|
||||
StartBlockNumber: chunk.Blocks[0].Header.Number.Uint64(),
|
||||
StartBlockHash: chunk.Blocks[0].Header.Hash().Hex(),
|
||||
EndBlockNumber: chunk.Blocks[numBlocks-1].Header.Number.Uint64(),
|
||||
EndBlockHash: chunk.Blocks[numBlocks-1].Header.Hash().Hex(),
|
||||
TotalL2TxGas: totalL2TxGas,
|
||||
TotalL2TxNum: totalL2TxNum,
|
||||
TotalL1CommitCalldataSize: totalL1CommitCalldataSize,
|
||||
TotalL1CommitGas: totalL1CommitGas,
|
||||
StartBlockTime: chunk.Blocks[0].Header.Time,
|
||||
TotalL1MessagesPoppedBefore: totalL1MessagePoppedBefore,
|
||||
TotalL1MessagesPoppedInChunk: chunk.NumL1Messages(totalL1MessagePoppedBefore),
|
||||
ProvingStatus: int16(types.ProvingTaskUnassigned),
|
||||
}
|
||||
|
||||
if err := db.Create(&newChunk).Error; err != nil {
|
||||
log.Error("failed to insert chunk", "hash", hash, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &newChunk, nil
|
||||
}
|
||||
|
||||
// UpdateProvingStatus updates the proving status of a chunk.
|
||||
func (o *Chunk) UpdateProvingStatus(ctx context.Context, hash string, status types.ProvingStatus, dbTX ...*gorm.DB) error {
|
||||
db := o.db
|
||||
if len(dbTX) > 0 && dbTX[0] != nil {
|
||||
db = dbTX[0]
|
||||
}
|
||||
|
||||
updateFields := make(map[string]interface{})
|
||||
updateFields["proving_status"] = int(status)
|
||||
|
||||
switch status {
|
||||
case types.ProvingTaskAssigned:
|
||||
updateFields["prover_assigned_at"] = time.Now()
|
||||
case types.ProvingTaskUnassigned:
|
||||
updateFields["prover_assigned_at"] = nil
|
||||
case types.ProvingTaskProved, types.ProvingTaskVerified:
|
||||
updateFields["proved_at"] = time.Now()
|
||||
default:
|
||||
}
|
||||
|
||||
if err := db.Model(&Chunk{}).Where("hash", hash).Updates(updateFields).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateBatchHashInRange updates the batch_hash for chunks within the specified range (inclusive).
|
||||
// The range is closed, i.e., it includes both start and end indices.
|
||||
func (o *Chunk) UpdateBatchHashInRange(ctx context.Context, startIndex uint64, endIndex uint64, batchHash string, dbTX ...*gorm.DB) error {
|
||||
db := o.db
|
||||
if len(dbTX) > 0 && dbTX[0] != nil {
|
||||
db = dbTX[0]
|
||||
}
|
||||
db = db.Model(&Chunk{}).Where("index >= ? AND index <= ?", startIndex, endIndex)
|
||||
|
||||
if err := db.Update("batch_hash", batchHash).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
)
|
||||
|
||||
// AddBatchInfoToDB inserts the batch information to the BlockBatch table and updates the batch_hash
|
||||
// in all blocks included in the batch.
|
||||
func AddBatchInfoToDB(db *gorm.DB, batchData *bridgeTypes.BatchData) error {
|
||||
blockBatch := NewBlockBatch(db)
|
||||
blockTrace := NewBlockTrace(db)
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
rowsAffected, dbTxErr := blockBatch.InsertBlockBatchByBatchData(tx, batchData)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
|
||||
var blockIDs = make([]uint64, len(batchData.Batch.Blocks))
|
||||
for i, block := range batchData.Batch.Blocks {
|
||||
blockIDs[i] = block.BlockNumber
|
||||
}
|
||||
|
||||
dbTxErr = blockTrace.UpdateBatchHashForL2Blocks(tx, blockIDs, batchData.Hash().Hex())
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
208
bridge/internal/orm/l2_block.go
Normal file
208
bridge/internal/orm/l2_block.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/bridge/internal/types"
|
||||
)
|
||||
|
||||
// L2Block represents a l2 block in the database.
|
||||
type L2Block struct {
|
||||
db *gorm.DB `gorm:"column:-"`
|
||||
|
||||
Number uint64 `json:"number" gorm:"number"`
|
||||
Hash string `json:"hash" gorm:"hash"`
|
||||
ParentHash string `json:"parent_hash" gorm:"parent_hash"`
|
||||
Header string `json:"header" gorm:"header"`
|
||||
Transactions string `json:"transactions" gorm:"transactions"`
|
||||
WithdrawTrieRoot string `json:"withdraw_trie_root" gorm:"withdraw_trie_root"`
|
||||
TxNum uint64 `json:"tx_num" gorm:"tx_num"`
|
||||
GasUsed uint64 `json:"gas_used" gorm:"gas_used"`
|
||||
BlockTimestamp uint64 `json:"block_timestamp" gorm:"block_timestamp"`
|
||||
ChunkHash string `json:"chunk_hash" gorm:"chunk_hash;default:NULL"`
|
||||
}
|
||||
|
||||
// NewL2Block creates a new L2Block instance
|
||||
func NewL2Block(db *gorm.DB) *L2Block {
|
||||
return &L2Block{db: db}
|
||||
}
|
||||
|
||||
// TableName returns the name of the "l2_block" table.
|
||||
func (*L2Block) TableName() string {
|
||||
return "l2_block"
|
||||
}
|
||||
|
||||
// GetL2BlocksLatestHeight retrieves the height of the latest L2 block.
|
||||
// If the l2_block table is empty, it returns 0 to represent the genesis block height.
|
||||
// In case of an error, it returns -1 along with the error.
|
||||
func (o *L2Block) GetL2BlocksLatestHeight(ctx context.Context) (int64, error) {
|
||||
var maxNumber int64
|
||||
if err := o.db.WithContext(ctx).Model(&L2Block{}).Select("COALESCE(MAX(number), 0)").Row().Scan(&maxNumber); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return maxNumber, nil
|
||||
}
|
||||
|
||||
// GetUnchunkedBlocks get the l2 blocks that have not been put into a chunk.
|
||||
// The returned blocks are sorted in ascending order by their block number.
|
||||
func (o *L2Block) GetUnchunkedBlocks(ctx context.Context) ([]*types.WrappedBlock, error) {
|
||||
var l2Blocks []L2Block
|
||||
if err := o.db.WithContext(ctx).Select("header, transactions, withdraw_trie_root").
|
||||
Where("chunk_hash IS NULL").
|
||||
Order("number asc").
|
||||
Find(&l2Blocks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wrappedBlocks []*types.WrappedBlock
|
||||
for _, v := range l2Blocks {
|
||||
var wrappedBlock types.WrappedBlock
|
||||
|
||||
if err := json.Unmarshal([]byte(v.Transactions), &wrappedBlock.Transactions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedBlock.Header = &gethTypes.Header{}
|
||||
if err := json.Unmarshal([]byte(v.Header), wrappedBlock.Header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedBlock.WithdrawTrieRoot = common.HexToHash(v.WithdrawTrieRoot)
|
||||
wrappedBlocks = append(wrappedBlocks, &wrappedBlock)
|
||||
}
|
||||
|
||||
return wrappedBlocks, nil
|
||||
}
|
||||
|
||||
// GetL2Blocks retrieves selected L2Blocks from the database.
|
||||
// The returned L2Blocks are sorted in ascending order by their block number.
|
||||
func (o *L2Block) GetL2Blocks(ctx context.Context, fields map[string]interface{}, orderByList []string, limit int) ([]*L2Block, error) {
|
||||
db := o.db.WithContext(ctx)
|
||||
|
||||
for key, value := range fields {
|
||||
db = db.Where(key, value)
|
||||
}
|
||||
|
||||
for _, orderBy := range orderByList {
|
||||
db = db.Order(orderBy)
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
db = db.Limit(limit)
|
||||
}
|
||||
|
||||
db = db.Order("number ASC")
|
||||
|
||||
var l2Blocks []*L2Block
|
||||
if err := db.Find(&l2Blocks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l2Blocks, nil
|
||||
}
|
||||
|
||||
// GetL2BlocksInRange retrieves the L2 blocks within the specified range (inclusive).
|
||||
// The range is closed, i.e., it includes both start and end block numbers.
|
||||
// The returned blocks are sorted in ascending order by their block number.
|
||||
func (o *L2Block) GetL2BlocksInRange(ctx context.Context, startBlockNumber uint64, endBlockNumber uint64) ([]*types.WrappedBlock, error) {
|
||||
if startBlockNumber > endBlockNumber {
|
||||
return nil, errors.New("start block number should be less than or equal to end block number")
|
||||
}
|
||||
|
||||
var l2Blocks []L2Block
|
||||
db := o.db.WithContext(ctx)
|
||||
db = db.Where("number >= ? AND number <= ?", startBlockNumber, endBlockNumber)
|
||||
db = db.Order("number ASC")
|
||||
|
||||
if err := db.Find(&l2Blocks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uint64(len(l2Blocks)) != endBlockNumber-startBlockNumber+1 {
|
||||
return nil, errors.New("number of blocks not expected in the specified range")
|
||||
}
|
||||
|
||||
var wrappedBlocks []*types.WrappedBlock
|
||||
for _, v := range l2Blocks {
|
||||
var wrappedBlock types.WrappedBlock
|
||||
|
||||
if err := json.Unmarshal([]byte(v.Transactions), &wrappedBlock.Transactions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedBlock.Header = &gethTypes.Header{}
|
||||
if err := json.Unmarshal([]byte(v.Header), wrappedBlock.Header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrappedBlock.WithdrawTrieRoot = common.HexToHash(v.WithdrawTrieRoot)
|
||||
wrappedBlocks = append(wrappedBlocks, &wrappedBlock)
|
||||
}
|
||||
|
||||
return wrappedBlocks, nil
|
||||
}
|
||||
|
||||
// InsertL2Blocks inserts l2 blocks into the "l2_block" table.
|
||||
func (o *L2Block) InsertL2Blocks(ctx context.Context, blocks []*types.WrappedBlock) error {
|
||||
var l2Blocks []L2Block
|
||||
for _, block := range blocks {
|
||||
header, err := json.Marshal(block.Header)
|
||||
if err != nil {
|
||||
log.Error("failed to marshal block header", "hash", block.Header.Hash().String(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
txs, err := json.Marshal(block.Transactions)
|
||||
if err != nil {
|
||||
log.Error("failed to marshal transactions", "hash", block.Header.Hash().String(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
l2Block := L2Block{
|
||||
Number: block.Header.Number.Uint64(),
|
||||
Hash: block.Header.Hash().String(),
|
||||
ParentHash: block.Header.ParentHash.String(),
|
||||
Transactions: string(txs),
|
||||
WithdrawTrieRoot: block.WithdrawTrieRoot.Hex(),
|
||||
TxNum: uint64(len(block.Transactions)),
|
||||
GasUsed: block.Header.GasUsed,
|
||||
BlockTimestamp: block.Header.Time,
|
||||
Header: string(header),
|
||||
}
|
||||
l2Blocks = append(l2Blocks, l2Block)
|
||||
}
|
||||
|
||||
if err := o.db.WithContext(ctx).Create(&l2Blocks).Error; err != nil {
|
||||
log.Error("failed to insert l2Blocks", "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateChunkHashInRange updates the chunk_hash of block tx within the specified range (inclusive).
|
||||
// The range is closed, i.e., it includes both start and end indices.
|
||||
// This function ensures the number of rows updated must equal to (endIndex - startIndex + 1).
|
||||
// If the rows affected do not match this expectation, an error is returned.
|
||||
func (o *L2Block) UpdateChunkHashInRange(ctx context.Context, startIndex uint64, endIndex uint64, chunkHash string, dbTX ...*gorm.DB) error {
|
||||
db := o.db
|
||||
if len(dbTX) > 0 && dbTX[0] != nil {
|
||||
db = dbTX[0]
|
||||
}
|
||||
|
||||
db = db.WithContext(ctx).Model(&L2Block{}).Where("number >= ? AND number <= ?", startIndex, endIndex)
|
||||
tx := db.Update("chunk_hash", chunkHash)
|
||||
|
||||
if tx.RowsAffected != int64(endIndex-startIndex+1) {
|
||||
return fmt.Errorf("expected %d rows to be updated, got %d", endIndex-startIndex+1, tx.RowsAffected)
|
||||
}
|
||||
|
||||
return tx.Error
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func testResetDB(t *testing.T) {
|
||||
cur, err := Current(pgDB.DB)
|
||||
assert.NoError(t, err)
|
||||
// total number of tables.
|
||||
assert.Equal(t, 5, int(cur))
|
||||
assert.Equal(t, 6, int(cur))
|
||||
}
|
||||
|
||||
func testMigrate(t *testing.T) {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
-- TODO: use foreign key for batch_id?
|
||||
-- TODO: why tx_num is bigint?
|
||||
create table block_trace
|
||||
(
|
||||
number BIGINT NOT NULL,
|
||||
hash VARCHAR NOT NULL,
|
||||
parent_hash VARCHAR NOT NULL,
|
||||
trace JSON NOT NULL,
|
||||
batch_hash VARCHAR DEFAULT NULL,
|
||||
tx_num INTEGER NOT NULL,
|
||||
gas_used BIGINT NOT NULL,
|
||||
block_timestamp NUMERIC NOT NULL
|
||||
);
|
||||
|
||||
create unique index block_trace_hash_uindex
|
||||
on block_trace (hash);
|
||||
|
||||
create unique index block_trace_number_uindex
|
||||
on block_trace (number);
|
||||
|
||||
create unique index block_trace_parent_uindex
|
||||
on block_trace (number, parent_hash);
|
||||
|
||||
create unique index block_trace_parent_hash_uindex
|
||||
on block_trace (hash, parent_hash);
|
||||
|
||||
create index block_trace_batch_hash_index
|
||||
on block_trace (batch_hash);
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists block_trace;
|
||||
-- +goose StatementEnd
|
||||
@@ -13,8 +13,8 @@ create table l1_message
|
||||
layer1_hash VARCHAR NOT NULL,
|
||||
layer2_hash VARCHAR DEFAULT NULL,
|
||||
status INTEGER DEFAULT 1,
|
||||
created_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
comment
|
||||
@@ -29,19 +29,6 @@ on l1_message (queue_index);
|
||||
create index l1_message_height_index
|
||||
on l1_message (height);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_time = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON l1_message FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
@@ -13,8 +13,8 @@ create table l2_message
|
||||
layer1_hash VARCHAR DEFAULT NULL,
|
||||
proof TEXT DEFAULT NULL,
|
||||
status INTEGER DEFAULT 1,
|
||||
created_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
comment
|
||||
@@ -29,19 +29,6 @@ on l2_message (nonce);
|
||||
create index l2_message_height_index
|
||||
on l2_message (height);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_time = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON l2_message FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
@@ -1,50 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
create table block_batch
|
||||
(
|
||||
hash VARCHAR NOT NULL,
|
||||
index BIGINT NOT NULL,
|
||||
start_block_number BIGINT NOT NULL,
|
||||
start_block_hash VARCHAR NOT NULL,
|
||||
end_block_number BIGINT NOT NULL,
|
||||
end_block_hash VARCHAR NOT NULL,
|
||||
parent_hash VARCHAR NOT NULL,
|
||||
state_root VARCHAR NOT NULL,
|
||||
total_tx_num BIGINT NOT NULL,
|
||||
total_l1_tx_num BIGINT NOT NULL,
|
||||
total_l2_gas BIGINT NOT NULL,
|
||||
proving_status INTEGER DEFAULT 1,
|
||||
proof BYTEA DEFAULT NULL,
|
||||
instance_commitments BYTEA DEFAULT NULL,
|
||||
proof_time_sec INTEGER DEFAULT 0,
|
||||
rollup_status INTEGER DEFAULT 1,
|
||||
commit_tx_hash VARCHAR DEFAULT NULL,
|
||||
finalize_tx_hash VARCHAR DEFAULT NULL,
|
||||
oracle_status INTEGER DEFAULT 1,
|
||||
oracle_tx_hash VARCHAR DEFAULT NULL,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
prover_assigned_at TIMESTAMP(0) DEFAULT NULL,
|
||||
proved_at TIMESTAMP(0) DEFAULT NULL,
|
||||
committed_at TIMESTAMP(0) DEFAULT NULL,
|
||||
finalized_at TIMESTAMP(0) DEFAULT NULL
|
||||
);
|
||||
|
||||
comment
|
||||
on column block_batch.proving_status is 'undefined, unassigned, skipped, assigned, proved, verified, failed';
|
||||
comment
|
||||
on column block_batch.rollup_status is 'undefined, pending, committing, committed, finalizing, finalized, finalization_skipped, commit_failed, finalize_failed';
|
||||
comment
|
||||
on column block_batch.oracle_status is 'undefined, pending, importing, imported, failed';
|
||||
|
||||
create unique index block_batch_hash_uindex
|
||||
on block_batch (hash);
|
||||
create unique index block_batch_index_uindex
|
||||
on block_batch (index);
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists block_batch;
|
||||
-- +goose StatementEnd
|
||||
32
bridge/internal/orm/migrate/migrations/00004_l2_block.sql
Normal file
32
bridge/internal/orm/migrate/migrations/00004_l2_block.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
create table l2_block
|
||||
(
|
||||
number BIGINT NOT NULL,
|
||||
hash VARCHAR NOT NULL,
|
||||
parent_hash VARCHAR NOT NULL,
|
||||
header TEXT NOT NULL,
|
||||
transactions TEXT NOT NULL,
|
||||
withdraw_trie_root VARCHAR NOT NULL,
|
||||
tx_num INTEGER NOT NULL,
|
||||
gas_used BIGINT NOT NULL,
|
||||
block_timestamp NUMERIC NOT NULL,
|
||||
chunk_hash VARCHAR DEFAULT NULL
|
||||
);
|
||||
|
||||
create unique index l2_block_hash_uindex
|
||||
on l2_block (hash);
|
||||
|
||||
create unique index l2_block_number_uindex
|
||||
on l2_block (number);
|
||||
|
||||
create index l2_block_chunk_hash_index
|
||||
on l2_block (chunk_hash);
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists l2_block;
|
||||
-- +goose StatementEnd
|
||||
54
bridge/internal/orm/migrate/migrations/00005_chunk.sql
Normal file
54
bridge/internal/orm/migrate/migrations/00005_chunk.sql
Normal file
@@ -0,0 +1,54 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
create table chunk
|
||||
(
|
||||
-- chunk
|
||||
index BIGINT NOT NULL,
|
||||
hash VARCHAR NOT NULL,
|
||||
start_block_number BIGINT NOT NULL,
|
||||
start_block_hash VARCHAR NOT NULL,
|
||||
end_block_number BIGINT NOT NULL,
|
||||
end_block_hash VARCHAR NOT NULL,
|
||||
total_l1_messages_popped_before BIGINT NOT NULL,
|
||||
total_l1_messages_popped_in_chunk BIGINT NOT NULL,
|
||||
start_block_time BIGINT NOT NULL,
|
||||
|
||||
-- proof
|
||||
proving_status SMALLINT NOT NULL DEFAULT 1,
|
||||
proof BYTEA DEFAULT NULL,
|
||||
prover_assigned_at TIMESTAMP(0) DEFAULT NULL,
|
||||
proved_at TIMESTAMP(0) DEFAULT NULL,
|
||||
proof_time_sec SMALLINT DEFAULT NULL,
|
||||
|
||||
-- batch
|
||||
batch_hash VARCHAR DEFAULT NULL,
|
||||
|
||||
-- metadata
|
||||
total_l2_tx_gas BIGINT NOT NULL,
|
||||
total_l2_tx_num BIGINT NOT NULL,
|
||||
total_l1_commit_calldata_size BIGINT NOT NULL,
|
||||
total_l1_commit_gas BIGINT NOT NULL,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP(0) DEFAULT NULL
|
||||
);
|
||||
|
||||
comment
|
||||
on column chunk.proving_status is 'undefined, unassigned, skipped, assigned, proved, verified, failed';
|
||||
|
||||
create unique index chunk_index_uindex
|
||||
on chunk (index);
|
||||
|
||||
create unique index chunk_hash_uindex
|
||||
on chunk (hash);
|
||||
|
||||
create index batch_hash_index
|
||||
on chunk (batch_hash);
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists chunk;
|
||||
-- +goose StatementEnd
|
||||
58
bridge/internal/orm/migrate/migrations/00006_batch.sql
Normal file
58
bridge/internal/orm/migrate/migrations/00006_batch.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
create table batch
|
||||
(
|
||||
-- batch
|
||||
index BIGINT NOT NULL,
|
||||
hash VARCHAR NOT NULL,
|
||||
start_chunk_index BIGINT NOT NULL,
|
||||
start_chunk_hash VARCHAR NOT NULL,
|
||||
end_chunk_index BIGINT NOT NULL,
|
||||
end_chunk_hash VARCHAR NOT NULL,
|
||||
state_root VARCHAR NOT NULL,
|
||||
withdraw_root VARCHAR NOT NULL,
|
||||
batch_header BYTEA NOT NULL,
|
||||
|
||||
-- proof
|
||||
proving_status SMALLINT NOT NULL DEFAULT 1,
|
||||
proof BYTEA DEFAULT NULL,
|
||||
prover_assigned_at TIMESTAMP(0) DEFAULT NULL,
|
||||
proved_at TIMESTAMP(0) DEFAULT NULL,
|
||||
proof_time_sec INTEGER DEFAULT NULL,
|
||||
|
||||
-- rollup
|
||||
rollup_status SMALLINT NOT NULL DEFAULT 1,
|
||||
commit_tx_hash VARCHAR DEFAULT NULL,
|
||||
committed_at TIMESTAMP(0) DEFAULT NULL,
|
||||
finalize_tx_hash VARCHAR DEFAULT NULL,
|
||||
finalized_at TIMESTAMP(0) DEFAULT NULL,
|
||||
|
||||
-- gas oracle
|
||||
oracle_status SMALLINT NOT NULL DEFAULT 1,
|
||||
oracle_tx_hash VARCHAR DEFAULT NULL,
|
||||
|
||||
-- metadata
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP(0) DEFAULT NULL
|
||||
);
|
||||
|
||||
create unique index batch_index_uindex
|
||||
on batch (index);
|
||||
|
||||
create unique index batch_hash_uindex
|
||||
on batch (hash);
|
||||
|
||||
comment
|
||||
on column batch.proving_status is 'undefined, unassigned, skipped, assigned, proved, verified, failed';
|
||||
|
||||
comment
|
||||
on column batch.rollup_status is 'undefined, pending, committing, committed, finalizing, finalized, finalization_skipped, commit_failed, finalize_failed';
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists batch;
|
||||
-- +goose StatementEnd
|
||||
283
bridge/internal/orm/orm_test.go
Normal file
283
bridge/internal/orm/orm_test.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
"scroll-tech/common/types"
|
||||
|
||||
"scroll-tech/bridge/internal/config"
|
||||
"scroll-tech/bridge/internal/orm/migrate"
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
"scroll-tech/bridge/internal/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
base *docker.App
|
||||
|
||||
db *gorm.DB
|
||||
l2BlockOrm *L2Block
|
||||
chunkOrm *Chunk
|
||||
batchOrm *Batch
|
||||
|
||||
wrappedBlock1 *bridgeTypes.WrappedBlock
|
||||
wrappedBlock2 *bridgeTypes.WrappedBlock
|
||||
chunk1 *bridgeTypes.Chunk
|
||||
chunk2 *bridgeTypes.Chunk
|
||||
chunkHash1 common.Hash
|
||||
chunkHash2 common.Hash
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
t := &testing.T{}
|
||||
setupEnv(t)
|
||||
defer tearDownEnv(t)
|
||||
m.Run()
|
||||
}
|
||||
|
||||
func setupEnv(t *testing.T) {
|
||||
base = docker.NewDockerApp()
|
||||
base.RunDBImage(t)
|
||||
var err error
|
||||
db, err = utils.InitDB(
|
||||
&config.DBConfig{
|
||||
DSN: base.DBConfig.DSN,
|
||||
DriverName: base.DBConfig.DriverName,
|
||||
MaxOpenNum: base.DBConfig.MaxOpenNum,
|
||||
MaxIdleNum: base.DBConfig.MaxIdleNum,
|
||||
},
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
sqlDB, err := db.DB()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(sqlDB))
|
||||
|
||||
batchOrm = NewBatch(db)
|
||||
chunkOrm = NewChunk(db)
|
||||
l2BlockOrm = NewL2Block(db)
|
||||
|
||||
templateBlockTrace, err := os.ReadFile("../../../common/testdata/blockTrace_02.json")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read file: %v", err)
|
||||
}
|
||||
wrappedBlock1 = &bridgeTypes.WrappedBlock{}
|
||||
if err = json.Unmarshal(templateBlockTrace, wrappedBlock1); err != nil {
|
||||
t.Fatalf("failed to unmarshal block trace: %v", err)
|
||||
}
|
||||
|
||||
templateBlockTrace, err = os.ReadFile("../../../common/testdata/blockTrace_03.json")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read file: %v", err)
|
||||
}
|
||||
wrappedBlock2 = &bridgeTypes.WrappedBlock{}
|
||||
if err = json.Unmarshal(templateBlockTrace, wrappedBlock2); err != nil {
|
||||
t.Fatalf("failed to unmarshal block trace: %v", err)
|
||||
}
|
||||
|
||||
chunk1 = &bridgeTypes.Chunk{Blocks: []*bridgeTypes.WrappedBlock{wrappedBlock1}}
|
||||
chunkHash1, err = chunk1.Hash(0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
chunk2 = &bridgeTypes.Chunk{Blocks: []*bridgeTypes.WrappedBlock{wrappedBlock2}}
|
||||
chunkHash2, err = chunk2.Hash(chunk1.NumL1Messages(0))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func tearDownEnv(t *testing.T) {
|
||||
sqlDB, err := db.DB()
|
||||
assert.NoError(t, err)
|
||||
sqlDB.Close()
|
||||
base.Free()
|
||||
}
|
||||
|
||||
func TestL2BlockOrm(t *testing.T) {
|
||||
sqlDB, err := db.DB()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(sqlDB))
|
||||
|
||||
err = l2BlockOrm.InsertL2Blocks(context.Background(), []*bridgeTypes.WrappedBlock{wrappedBlock1, wrappedBlock2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
height, err := l2BlockOrm.GetL2BlocksLatestHeight(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), height)
|
||||
|
||||
blocks, err := l2BlockOrm.GetUnchunkedBlocks(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, blocks, 2)
|
||||
assert.Equal(t, wrappedBlock1, blocks[0])
|
||||
assert.Equal(t, wrappedBlock2, blocks[1])
|
||||
|
||||
blocks, err = l2BlockOrm.GetL2BlocksInRange(context.Background(), 2, 3)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, blocks, 2)
|
||||
assert.Equal(t, wrappedBlock1, blocks[0])
|
||||
assert.Equal(t, wrappedBlock2, blocks[1])
|
||||
|
||||
err = l2BlockOrm.UpdateChunkHashInRange(context.Background(), 2, 2, "test hash")
|
||||
assert.NoError(t, err)
|
||||
|
||||
blocks, err = l2BlockOrm.GetUnchunkedBlocks(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, blocks, 1)
|
||||
assert.Equal(t, wrappedBlock2, blocks[0])
|
||||
}
|
||||
|
||||
func TestChunkOrm(t *testing.T) {
|
||||
sqlDB, err := db.DB()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(sqlDB))
|
||||
|
||||
err = l2BlockOrm.InsertL2Blocks(context.Background(), []*bridgeTypes.WrappedBlock{wrappedBlock1, wrappedBlock2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbChunk1, err := chunkOrm.InsertChunk(context.Background(), chunk1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dbChunk1.Hash, chunkHash1.Hex())
|
||||
|
||||
dbChunk2, err := chunkOrm.InsertChunk(context.Background(), chunk2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dbChunk2.Hash, chunkHash2.Hex())
|
||||
|
||||
chunks, err := chunkOrm.GetUnbatchedChunks(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, chunks, 2)
|
||||
assert.Equal(t, chunkHash1.Hex(), chunks[0].Hash)
|
||||
assert.Equal(t, chunkHash2.Hex(), chunks[1].Hash)
|
||||
|
||||
err = chunkOrm.UpdateProvingStatus(context.Background(), chunkHash1.Hex(), types.ProvingTaskVerified)
|
||||
assert.NoError(t, err)
|
||||
err = chunkOrm.UpdateProvingStatus(context.Background(), chunkHash2.Hex(), types.ProvingTaskAssigned)
|
||||
assert.NoError(t, err)
|
||||
|
||||
chunks, err = chunkOrm.GetChunksInRange(context.Background(), 0, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, chunks, 2)
|
||||
assert.Equal(t, chunkHash1.Hex(), chunks[0].Hash)
|
||||
assert.Equal(t, chunkHash2.Hex(), chunks[1].Hash)
|
||||
assert.Equal(t, types.ProvingTaskVerified, types.ProvingStatus(chunks[0].ProvingStatus))
|
||||
assert.Equal(t, types.ProvingTaskAssigned, types.ProvingStatus(chunks[1].ProvingStatus))
|
||||
|
||||
err = chunkOrm.UpdateBatchHashInRange(context.Background(), 0, 0, "test hash")
|
||||
assert.NoError(t, err)
|
||||
chunks, err = chunkOrm.GetUnbatchedChunks(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, chunks, 1)
|
||||
}
|
||||
|
||||
func TestBatchOrm(t *testing.T) {
|
||||
sqlDB, err := db.DB()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(sqlDB))
|
||||
|
||||
err = l2BlockOrm.InsertL2Blocks(context.Background(), []*bridgeTypes.WrappedBlock{wrappedBlock1, wrappedBlock2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbChunk1, err := chunkOrm.InsertChunk(context.Background(), chunk1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dbChunk1.Hash, chunkHash1.Hex())
|
||||
|
||||
dbChunk2, err := chunkOrm.InsertChunk(context.Background(), chunk2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dbChunk2.Hash, chunkHash2.Hex())
|
||||
|
||||
batch1, err := batchOrm.InsertBatch(context.Background(), 0, 0, chunkHash1.Hex(), chunkHash1.Hex(), []*bridgeTypes.Chunk{chunk1})
|
||||
assert.NoError(t, err)
|
||||
hash1 := batch1.Hash
|
||||
|
||||
batch1, err = batchOrm.GetBatchByIndex(context.Background(), 0)
|
||||
assert.NoError(t, err)
|
||||
batchHeader1, err := bridgeTypes.DecodeBatchHeader(batch1.BatchHeader)
|
||||
assert.NoError(t, err)
|
||||
batchHash1 := batchHeader1.Hash().Hex()
|
||||
assert.Equal(t, hash1, batchHash1)
|
||||
|
||||
batch2, err := batchOrm.InsertBatch(context.Background(), 1, 1, chunkHash2.Hex(), chunkHash2.Hex(), []*bridgeTypes.Chunk{chunk2})
|
||||
assert.NoError(t, err)
|
||||
hash2 := batch2.Hash
|
||||
|
||||
batch2, err = batchOrm.GetBatchByIndex(context.Background(), 1)
|
||||
assert.NoError(t, err)
|
||||
batchHeader2, err := bridgeTypes.DecodeBatchHeader(batch2.BatchHeader)
|
||||
assert.NoError(t, err)
|
||||
batchHash2 := batchHeader2.Hash().Hex()
|
||||
assert.Equal(t, hash2, batchHash2)
|
||||
|
||||
count, err := batchOrm.GetBatchCount(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(2), count)
|
||||
|
||||
pendingBatches, err := batchOrm.GetPendingBatches(context.Background(), 100)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(pendingBatches))
|
||||
|
||||
rollupStatus, err := batchOrm.GetRollupStatusByHashList(context.Background(), []string{batchHash1, batchHash2})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(rollupStatus))
|
||||
assert.Equal(t, types.RollupPending, rollupStatus[0])
|
||||
assert.Equal(t, types.RollupPending, rollupStatus[1])
|
||||
|
||||
err = batchOrm.UpdateProvingStatus(context.Background(), batchHash1, types.ProvingTaskSkipped)
|
||||
assert.NoError(t, err)
|
||||
err = batchOrm.UpdateRollupStatus(context.Background(), batchHash1, types.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
err = batchOrm.UpdateProvingStatus(context.Background(), batchHash2, types.ProvingTaskFailed)
|
||||
assert.NoError(t, err)
|
||||
err = batchOrm.UpdateRollupStatus(context.Background(), batchHash2, types.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
|
||||
count, err = batchOrm.UpdateSkippedBatches(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(2), count)
|
||||
|
||||
count, err = batchOrm.UpdateSkippedBatches(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(0), count)
|
||||
|
||||
batch, err := batchOrm.GetBatchByIndex(context.Background(), 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.RollupFinalizationSkipped, types.RollupStatus(batch.RollupStatus))
|
||||
|
||||
err = batchOrm.UpdateProvingStatus(context.Background(), batchHash2, types.ProvingTaskVerified)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbProof, err := batchOrm.GetVerifiedProofByHash(context.Background(), batchHash1)
|
||||
assert.Error(t, err, gorm.ErrRecordNotFound)
|
||||
assert.Nil(t, dbProof)
|
||||
|
||||
err = batchOrm.UpdateProvingStatus(context.Background(), batchHash2, types.ProvingTaskVerified)
|
||||
assert.NoError(t, err)
|
||||
err = batchOrm.UpdateRollupStatus(context.Background(), batchHash2, types.RollupFinalized)
|
||||
assert.NoError(t, err)
|
||||
err = batchOrm.UpdateL2GasOracleStatusAndOracleTxHash(context.Background(), batchHash2, types.GasOracleImported, "oracleTxHash")
|
||||
assert.NoError(t, err)
|
||||
|
||||
updatedBatch, err := batchOrm.GetLatestBatch(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.ProvingTaskVerified, types.ProvingStatus(updatedBatch.ProvingStatus))
|
||||
assert.Equal(t, types.RollupFinalized, types.RollupStatus(updatedBatch.RollupStatus))
|
||||
assert.Equal(t, types.GasOracleImported, types.GasOracleStatus(updatedBatch.OracleStatus))
|
||||
assert.Equal(t, "oracleTxHash", updatedBatch.OracleTxHash)
|
||||
|
||||
err = batchOrm.UpdateCommitTxHashAndRollupStatus(context.Background(), batchHash2, "commitTxHash", types.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
updatedBatch, err = batchOrm.GetLatestBatch(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "commitTxHash", updatedBatch.CommitTxHash)
|
||||
assert.Equal(t, types.RollupCommitted, types.RollupStatus(updatedBatch.RollupStatus))
|
||||
|
||||
err = batchOrm.UpdateFinalizeTxHashAndRollupStatus(context.Background(), batchHash2, "finalizeTxHash", types.RollupFinalizeFailed)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updatedBatch, err = batchOrm.GetLatestBatch(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "finalizeTxHash", updatedBatch.FinalizeTxHash)
|
||||
assert.Equal(t, types.RollupFinalizeFailed, types.RollupStatus(updatedBatch.RollupStatus))
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
|
||||
abi "scroll-tech/bridge/abi"
|
||||
)
|
||||
|
||||
// PublicInputHashConfig is the configuration of how to compute the public input hash.
|
||||
type PublicInputHashConfig struct {
|
||||
MaxTxNum int `json:"max_tx_num"`
|
||||
PaddingTxHash common.Hash `json:"padding_tx_hash"`
|
||||
}
|
||||
|
||||
const defaultMaxTxNum = 44
|
||||
|
||||
var defaultPaddingTxHash = [32]byte{}
|
||||
|
||||
// BatchData contains info of batch to be committed.
|
||||
type BatchData struct {
|
||||
Batch abi.IScrollChainBatch
|
||||
TxHashes []common.Hash
|
||||
TotalTxNum uint64
|
||||
TotalL1TxNum uint64
|
||||
TotalL2Gas uint64
|
||||
|
||||
// cache for the BatchHash
|
||||
hash *common.Hash
|
||||
// The config to compute the public input hash, or the block hash.
|
||||
// If it is nil, the hash calculation will use `defaultMaxTxNum` and `defaultPaddingTxHash`.
|
||||
piCfg *PublicInputHashConfig
|
||||
}
|
||||
|
||||
// Timestamp returns the timestamp of the first block in the BlockData.
|
||||
func (b *BatchData) Timestamp() uint64 {
|
||||
if len(b.Batch.Blocks) == 0 {
|
||||
return 0
|
||||
}
|
||||
return b.Batch.Blocks[0].Timestamp
|
||||
}
|
||||
|
||||
// Hash calculates the hash of this batch.
|
||||
func (b *BatchData) Hash() *common.Hash {
|
||||
if b.hash != nil {
|
||||
return b.hash
|
||||
}
|
||||
|
||||
buf := make([]byte, 8)
|
||||
hasher := crypto.NewKeccakState()
|
||||
|
||||
// 1. hash PrevStateRoot, NewStateRoot, WithdrawTrieRoot
|
||||
// @todo: panic on error here.
|
||||
_, _ = hasher.Write(b.Batch.PrevStateRoot[:])
|
||||
_, _ = hasher.Write(b.Batch.NewStateRoot[:])
|
||||
_, _ = hasher.Write(b.Batch.WithdrawTrieRoot[:])
|
||||
|
||||
// 2. hash all block contexts
|
||||
for _, block := range b.Batch.Blocks {
|
||||
// write BlockHash & ParentHash
|
||||
_, _ = hasher.Write(block.BlockHash[:])
|
||||
_, _ = hasher.Write(block.ParentHash[:])
|
||||
// write BlockNumber
|
||||
binary.BigEndian.PutUint64(buf, block.BlockNumber)
|
||||
_, _ = hasher.Write(buf)
|
||||
// write Timestamp
|
||||
binary.BigEndian.PutUint64(buf, block.Timestamp)
|
||||
_, _ = hasher.Write(buf)
|
||||
// write BaseFee
|
||||
var baseFee [32]byte
|
||||
if block.BaseFee != nil {
|
||||
baseFee = newByte32FromBytes(block.BaseFee.Bytes())
|
||||
}
|
||||
_, _ = hasher.Write(baseFee[:])
|
||||
// write GasLimit
|
||||
binary.BigEndian.PutUint64(buf, block.GasLimit)
|
||||
_, _ = hasher.Write(buf)
|
||||
// write NumTransactions
|
||||
binary.BigEndian.PutUint16(buf[:2], block.NumTransactions)
|
||||
_, _ = hasher.Write(buf[:2])
|
||||
// write NumL1Messages
|
||||
binary.BigEndian.PutUint16(buf[:2], block.NumL1Messages)
|
||||
_, _ = hasher.Write(buf[:2])
|
||||
}
|
||||
|
||||
// 3. add all tx hashes
|
||||
for _, txHash := range b.TxHashes {
|
||||
_, _ = hasher.Write(txHash[:])
|
||||
}
|
||||
|
||||
// 4. append empty tx hash up to MaxTxNum
|
||||
maxTxNum := defaultMaxTxNum
|
||||
paddingTxHash := common.Hash(defaultPaddingTxHash)
|
||||
if b.piCfg != nil {
|
||||
maxTxNum = b.piCfg.MaxTxNum
|
||||
paddingTxHash = b.piCfg.PaddingTxHash
|
||||
}
|
||||
for i := len(b.TxHashes); i < maxTxNum; i++ {
|
||||
_, _ = hasher.Write(paddingTxHash[:])
|
||||
}
|
||||
|
||||
b.hash = new(common.Hash)
|
||||
_, _ = hasher.Read(b.hash[:])
|
||||
|
||||
return b.hash
|
||||
}
|
||||
|
||||
// NewBatchData creates a BatchData given the parent batch information and the traces of the blocks
|
||||
// included in this batch
|
||||
func NewBatchData(parentBatch *BatchInfo, blocks []*WrappedBlock, piCfg *PublicInputHashConfig) *BatchData {
|
||||
batchData := new(BatchData)
|
||||
batch := &batchData.Batch
|
||||
|
||||
// set BatchIndex, ParentBatchHash
|
||||
batch.BatchIndex = parentBatch.Index + 1
|
||||
batch.ParentBatchHash = common.HexToHash(parentBatch.Hash)
|
||||
batch.Blocks = make([]abi.IScrollChainBlockContext, len(blocks))
|
||||
|
||||
var batchTxDataBuf bytes.Buffer
|
||||
batchTxDataWriter := bufio.NewWriter(&batchTxDataBuf)
|
||||
|
||||
for i, block := range blocks {
|
||||
batchData.TotalTxNum += uint64(len(block.Transactions))
|
||||
batchData.TotalL2Gas += block.Header.GasUsed
|
||||
|
||||
// set baseFee to 0 when it's nil in the block header
|
||||
baseFee := block.Header.BaseFee
|
||||
if baseFee == nil {
|
||||
baseFee = big.NewInt(0)
|
||||
}
|
||||
|
||||
batch.Blocks[i] = abi.IScrollChainBlockContext{
|
||||
BlockHash: block.Header.Hash(),
|
||||
ParentHash: block.Header.ParentHash,
|
||||
BlockNumber: block.Header.Number.Uint64(),
|
||||
Timestamp: block.Header.Time,
|
||||
BaseFee: baseFee,
|
||||
GasLimit: block.Header.GasLimit,
|
||||
NumTransactions: uint16(len(block.Transactions)),
|
||||
NumL1Messages: 0, // TODO: currently use 0, will re-enable after we use l2geth to include L1 messages
|
||||
}
|
||||
|
||||
// fill in RLP-encoded transactions
|
||||
for _, txData := range block.Transactions {
|
||||
data, _ := hexutil.Decode(txData.Data)
|
||||
// right now we only support legacy tx
|
||||
tx := types.NewTx(&types.LegacyTx{
|
||||
Nonce: txData.Nonce,
|
||||
To: txData.To,
|
||||
Value: txData.Value.ToInt(),
|
||||
Gas: txData.Gas,
|
||||
GasPrice: txData.GasPrice.ToInt(),
|
||||
Data: data,
|
||||
V: txData.V.ToInt(),
|
||||
R: txData.R.ToInt(),
|
||||
S: txData.S.ToInt(),
|
||||
})
|
||||
rlpTxData, _ := tx.MarshalBinary()
|
||||
var txLen [4]byte
|
||||
binary.BigEndian.PutUint32(txLen[:], uint32(len(rlpTxData)))
|
||||
_, _ = batchTxDataWriter.Write(txLen[:])
|
||||
_, _ = batchTxDataWriter.Write(rlpTxData)
|
||||
batchData.TxHashes = append(batchData.TxHashes, tx.Hash())
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
batch.PrevStateRoot = common.HexToHash(parentBatch.StateRoot)
|
||||
}
|
||||
|
||||
// set NewStateRoot & WithdrawTrieRoot from the last block
|
||||
if i == len(blocks)-1 {
|
||||
batch.NewStateRoot = block.Header.Root
|
||||
batch.WithdrawTrieRoot = block.WithdrawTrieRoot
|
||||
}
|
||||
}
|
||||
|
||||
if err := batchTxDataWriter.Flush(); err != nil {
|
||||
panic("Buffered I/O flush failed")
|
||||
}
|
||||
|
||||
batch.L2Transactions = batchTxDataBuf.Bytes()
|
||||
batchData.piCfg = piCfg
|
||||
|
||||
return batchData
|
||||
}
|
||||
|
||||
// NewGenesisBatchData generates the batch that contains the genesis block.
|
||||
func NewGenesisBatchData(genesisBlockTrace *WrappedBlock) *BatchData {
|
||||
header := genesisBlockTrace.Header
|
||||
if header.Number.Uint64() != 0 {
|
||||
panic("invalid genesis block trace: block number is not 0")
|
||||
}
|
||||
|
||||
batchData := new(BatchData)
|
||||
batch := &batchData.Batch
|
||||
|
||||
// fill in batch information
|
||||
batch.BatchIndex = 0
|
||||
batch.Blocks = make([]abi.IScrollChainBlockContext, 1)
|
||||
batch.NewStateRoot = header.Root
|
||||
// PrevStateRoot, WithdrawTrieRoot, ParentBatchHash should all be 0
|
||||
// L2Transactions should be empty
|
||||
|
||||
// fill in block context
|
||||
batch.Blocks[0] = abi.IScrollChainBlockContext{
|
||||
BlockHash: header.Hash(),
|
||||
ParentHash: header.ParentHash,
|
||||
BlockNumber: header.Number.Uint64(),
|
||||
Timestamp: header.Time,
|
||||
BaseFee: header.BaseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
NumTransactions: 0,
|
||||
NumL1Messages: 0,
|
||||
}
|
||||
|
||||
return batchData
|
||||
}
|
||||
|
||||
// newByte32FromBytes converts the bytes in big-endian encoding to 32 bytes in big-endian encoding
|
||||
func newByte32FromBytes(b []byte) [32]byte {
|
||||
var byte32 [32]byte
|
||||
|
||||
if len(b) > 32 {
|
||||
b = b[len(b)-32:]
|
||||
}
|
||||
|
||||
copy(byte32[32-len(b):], b)
|
||||
return byte32
|
||||
}
|
||||
158
bridge/internal/types/batch_header.go
Normal file
158
bridge/internal/types/batch_header.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// BatchHeader contains batch header info to be committed.
|
||||
type BatchHeader struct {
|
||||
// Encoded in BatchHeaderV0Codec
|
||||
version uint8
|
||||
batchIndex uint64
|
||||
l1MessagePopped uint64
|
||||
totalL1MessagePopped uint64
|
||||
dataHash common.Hash
|
||||
parentBatchHash common.Hash
|
||||
skippedL1MessageBitmap []byte
|
||||
}
|
||||
|
||||
// NewBatchHeader creates a new BatchHeader
|
||||
func NewBatchHeader(version uint8, batchIndex, totalL1MessagePoppedBefore uint64, parentBatchHash common.Hash, chunks []*Chunk) (*BatchHeader, error) {
|
||||
// buffer for storing chunk hashes in order to compute the batch data hash
|
||||
var dataBytes []byte
|
||||
|
||||
// skipped L1 message bitmap, an array of 256-bit bitmaps
|
||||
var skippedBitmap []*big.Int
|
||||
|
||||
// the first queue index that belongs to this batch
|
||||
baseIndex := totalL1MessagePoppedBefore
|
||||
|
||||
// the next queue index that we need to process
|
||||
nextIndex := totalL1MessagePoppedBefore
|
||||
|
||||
for _, chunk := range chunks {
|
||||
// build data hash
|
||||
totalL1MessagePoppedBeforeChunk := nextIndex
|
||||
chunkHash, err := chunk.Hash(totalL1MessagePoppedBeforeChunk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataBytes = append(dataBytes, chunkHash.Bytes()...)
|
||||
|
||||
// build skip bitmap
|
||||
for _, block := range chunk.Blocks {
|
||||
for _, tx := range block.Transactions {
|
||||
if tx.Type != types.L1MessageTxType {
|
||||
continue
|
||||
}
|
||||
currentIndex := tx.Nonce
|
||||
|
||||
if currentIndex < nextIndex {
|
||||
return nil, fmt.Errorf("unexpected batch payload, expected queue index: %d, got: %d", nextIndex, currentIndex)
|
||||
}
|
||||
|
||||
// mark skipped messages
|
||||
for skippedIndex := nextIndex; skippedIndex < currentIndex; skippedIndex++ {
|
||||
quo := int((skippedIndex - baseIndex) / 256)
|
||||
rem := int((skippedIndex - baseIndex) % 256)
|
||||
for len(skippedBitmap) <= quo {
|
||||
bitmap := big.NewInt(0)
|
||||
skippedBitmap = append(skippedBitmap, bitmap)
|
||||
}
|
||||
skippedBitmap[quo].SetBit(skippedBitmap[quo], rem, 1)
|
||||
}
|
||||
|
||||
// process included message
|
||||
quo := int((currentIndex - baseIndex) / 256)
|
||||
for len(skippedBitmap) <= quo {
|
||||
bitmap := big.NewInt(0)
|
||||
skippedBitmap = append(skippedBitmap, bitmap)
|
||||
}
|
||||
|
||||
nextIndex = currentIndex + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compute data hash
|
||||
dataHash := crypto.Keccak256Hash(dataBytes)
|
||||
|
||||
// compute skipped bitmap
|
||||
bitmapBytes := make([]byte, len(skippedBitmap)*32)
|
||||
for ii, num := range skippedBitmap {
|
||||
bytes := num.Bytes()
|
||||
padding := 32 - len(bytes)
|
||||
copy(bitmapBytes[32*ii+padding:], bytes)
|
||||
}
|
||||
|
||||
return &BatchHeader{
|
||||
version: version,
|
||||
batchIndex: batchIndex,
|
||||
l1MessagePopped: nextIndex - totalL1MessagePoppedBefore,
|
||||
totalL1MessagePopped: nextIndex,
|
||||
dataHash: dataHash,
|
||||
parentBatchHash: parentBatchHash,
|
||||
skippedL1MessageBitmap: bitmapBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Version returns the version of the BatchHeader.
|
||||
func (b *BatchHeader) Version() uint8 {
|
||||
return b.version
|
||||
}
|
||||
|
||||
// BatchIndex returns the batch index of the BatchHeader.
|
||||
func (b *BatchHeader) BatchIndex() uint64 {
|
||||
return b.batchIndex
|
||||
}
|
||||
|
||||
// TotalL1MessagePopped returns the total number of L1 messages popped in the BatchHeader.
|
||||
func (b *BatchHeader) TotalL1MessagePopped() uint64 {
|
||||
return b.totalL1MessagePopped
|
||||
}
|
||||
|
||||
// SkippedL1MessageBitmap returns the skipped L1 message bitmap in the BatchHeader.
|
||||
func (b *BatchHeader) SkippedL1MessageBitmap() []byte {
|
||||
return b.skippedL1MessageBitmap
|
||||
}
|
||||
|
||||
// Encode encodes the BatchHeader into RollupV2 BatchHeaderV0Codec Encoding.
|
||||
func (b *BatchHeader) Encode() []byte {
|
||||
batchBytes := make([]byte, 89+len(b.skippedL1MessageBitmap))
|
||||
batchBytes[0] = b.version
|
||||
binary.BigEndian.PutUint64(batchBytes[1:], b.batchIndex)
|
||||
binary.BigEndian.PutUint64(batchBytes[9:], b.l1MessagePopped)
|
||||
binary.BigEndian.PutUint64(batchBytes[17:], b.totalL1MessagePopped)
|
||||
copy(batchBytes[25:], b.dataHash[:])
|
||||
copy(batchBytes[57:], b.parentBatchHash[:])
|
||||
copy(batchBytes[89:], b.skippedL1MessageBitmap[:])
|
||||
return batchBytes
|
||||
}
|
||||
|
||||
// Hash calculates the hash of the batch header.
|
||||
func (b *BatchHeader) Hash() common.Hash {
|
||||
return crypto.Keccak256Hash(b.Encode())
|
||||
}
|
||||
|
||||
// DecodeBatchHeader attempts to decode the given byte slice into a BatchHeader.
|
||||
func DecodeBatchHeader(data []byte) (*BatchHeader, error) {
|
||||
if len(data) < 89 {
|
||||
return nil, fmt.Errorf("insufficient data for BatchHeader")
|
||||
}
|
||||
b := &BatchHeader{
|
||||
version: data[0],
|
||||
batchIndex: binary.BigEndian.Uint64(data[1:9]),
|
||||
l1MessagePopped: binary.BigEndian.Uint64(data[9:17]),
|
||||
totalL1MessagePopped: binary.BigEndian.Uint64(data[17:25]),
|
||||
dataHash: common.BytesToHash(data[25:57]),
|
||||
parentBatchHash: common.BytesToHash(data[57:89]),
|
||||
skippedL1MessageBitmap: data[89:],
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
251
bridge/internal/types/batch_header_test.go
Normal file
251
bridge/internal/types/batch_header_test.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewBatchHeader(t *testing.T) {
|
||||
// Without L1 Msg
|
||||
templateBlockTrace, err := os.ReadFile("../../../common/testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace, wrappedBlock))
|
||||
chunk := &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock,
|
||||
},
|
||||
}
|
||||
parentBatchHeader := &BatchHeader{
|
||||
version: 1,
|
||||
batchIndex: 0,
|
||||
l1MessagePopped: 0,
|
||||
totalL1MessagePopped: 0,
|
||||
dataHash: common.HexToHash("0x0"),
|
||||
parentBatchHash: common.HexToHash("0x0"),
|
||||
skippedL1MessageBitmap: nil,
|
||||
}
|
||||
batchHeader, err := NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
assert.Equal(t, 0, len(batchHeader.skippedL1MessageBitmap))
|
||||
|
||||
// 1 L1 Msg in 1 bitmap
|
||||
templateBlockTrace2, err := os.ReadFile("../../../common/testdata/blockTrace_04.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock2 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace2, wrappedBlock2))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
|
||||
expectedBitmap := "00000000000000000000000000000000000000000000000000000000000003ff" // skip first 10
|
||||
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
|
||||
|
||||
// many consecutive L1 Msgs in 1 bitmap, no leading skipped msgs
|
||||
templateBlockTrace3, err := os.ReadFile("../../../common/testdata/blockTrace_05.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock3 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace3, wrappedBlock3))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock3,
|
||||
},
|
||||
}
|
||||
batchHeader, err = NewBatchHeader(1, 1, 37, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
assert.Equal(t, uint64(5), batchHeader.l1MessagePopped)
|
||||
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
|
||||
expectedBitmap = "0000000000000000000000000000000000000000000000000000000000000000" // all bits are included, so none are skipped
|
||||
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
|
||||
|
||||
// many consecutive L1 Msgs in 1 bitmap, with leading skipped msgs
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock3,
|
||||
},
|
||||
}
|
||||
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
assert.Equal(t, uint64(42), batchHeader.l1MessagePopped)
|
||||
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
|
||||
expectedBitmap = "0000000000000000000000000000000000000000000000000000001fffffffff" // skipped the first 37 messages
|
||||
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
|
||||
|
||||
// many sparse L1 Msgs in 1 bitmap
|
||||
templateBlockTrace4, err := os.ReadFile("../../../common/testdata/blockTrace_06.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock4 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace4, wrappedBlock4))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock4,
|
||||
},
|
||||
}
|
||||
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
assert.Equal(t, uint64(10), batchHeader.l1MessagePopped)
|
||||
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
|
||||
expectedBitmap = "00000000000000000000000000000000000000000000000000000000000001dd" // 0111011101
|
||||
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
|
||||
|
||||
// many L1 Msgs in each of 2 bitmaps
|
||||
templateBlockTrace5, err := os.ReadFile("../../../common/testdata/blockTrace_07.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock5 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace5, wrappedBlock5))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock5,
|
||||
},
|
||||
}
|
||||
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
assert.Equal(t, uint64(257), batchHeader.l1MessagePopped)
|
||||
assert.Equal(t, 64, len(batchHeader.skippedL1MessageBitmap))
|
||||
expectedBitmap = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0000000000000000000000000000000000000000000000000000000000000000"
|
||||
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
|
||||
}
|
||||
|
||||
func TestBatchHeaderEncode(t *testing.T) {
|
||||
// Without L1 Msg
|
||||
templateBlockTrace, err := os.ReadFile("../../../common/testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace, wrappedBlock))
|
||||
chunk := &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock,
|
||||
},
|
||||
}
|
||||
parentBatchHeader := &BatchHeader{
|
||||
version: 1,
|
||||
batchIndex: 0,
|
||||
l1MessagePopped: 0,
|
||||
totalL1MessagePopped: 0,
|
||||
dataHash: common.HexToHash("0x0"),
|
||||
parentBatchHash: common.HexToHash("0x0"),
|
||||
skippedL1MessageBitmap: nil,
|
||||
}
|
||||
batchHeader, err := NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
bytes := batchHeader.Encode()
|
||||
assert.Equal(t, 89, len(bytes))
|
||||
assert.Equal(t, "0100000000000000010000000000000000000000000000000010a64c9bd905f8caf5d668fbda622d6558c5a42cdb4b3895709743d159c22e534136709aabc8a23aa17fbcc833da2f7857d3c2884feec9aae73429c135f94985", common.Bytes2Hex(bytes))
|
||||
|
||||
// With L1 Msg
|
||||
templateBlockTrace2, err := os.ReadFile("../../../common/testdata/blockTrace_04.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock2 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace2, wrappedBlock2))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
bytes = batchHeader.Encode()
|
||||
assert.Equal(t, 121, len(bytes))
|
||||
assert.Equal(t, "010000000000000001000000000000000b000000000000000b457a9e90e8e51ba2de2f66c6b589540b88cf594dac7fa7d04b99cdcfecf24e384136709aabc8a23aa17fbcc833da2f7857d3c2884feec9aae73429c135f9498500000000000000000000000000000000000000000000000000000000000003ff", common.Bytes2Hex(bytes))
|
||||
}
|
||||
|
||||
func TestBatchHeaderHash(t *testing.T) {
|
||||
// Without L1 Msg
|
||||
templateBlockTrace, err := os.ReadFile("../../../common/testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace, wrappedBlock))
|
||||
chunk := &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock,
|
||||
},
|
||||
}
|
||||
parentBatchHeader := &BatchHeader{
|
||||
version: 1,
|
||||
batchIndex: 0,
|
||||
l1MessagePopped: 0,
|
||||
totalL1MessagePopped: 0,
|
||||
dataHash: common.HexToHash("0x0"),
|
||||
parentBatchHash: common.HexToHash("0x0"),
|
||||
skippedL1MessageBitmap: nil,
|
||||
}
|
||||
batchHeader, err := NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
hash := batchHeader.Hash()
|
||||
assert.Equal(t, "d69da4357da0073f4093c76e49f077e21bb52f48f57ee3e1fbd9c38a2881af81", common.Bytes2Hex(hash.Bytes()))
|
||||
|
||||
templateBlockTrace, err = os.ReadFile("../../../common/testdata/blockTrace_03.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock2 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace, wrappedBlock2))
|
||||
chunk2 := &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
batchHeader2, err := NewBatchHeader(1, 2, 0, batchHeader.Hash(), []*Chunk{chunk2})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader2)
|
||||
hash2 := batchHeader2.Hash()
|
||||
assert.Equal(t, "34de600163aa745d4513113137a5b54960d13f0d3f2849e490c4b875028bf930", common.Bytes2Hex(hash2.Bytes()))
|
||||
|
||||
// With L1 Msg
|
||||
templateBlockTrace3, err := os.ReadFile("../../../common/testdata/blockTrace_04.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock3 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace3, wrappedBlock3))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock3,
|
||||
},
|
||||
}
|
||||
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, batchHeader)
|
||||
hash = batchHeader.Hash()
|
||||
assert.Equal(t, "0ec9547c6645d5f0c1254e121f49e93f54525cfda5bfb2236440fb3470f48902", common.Bytes2Hex(hash.Bytes()))
|
||||
}
|
||||
|
||||
func TestBatchHeaderDecode(t *testing.T) {
|
||||
header := &BatchHeader{
|
||||
version: 1,
|
||||
batchIndex: 10,
|
||||
l1MessagePopped: 20,
|
||||
totalL1MessagePopped: 30,
|
||||
dataHash: common.HexToHash("0x01"),
|
||||
parentBatchHash: common.HexToHash("0x02"),
|
||||
skippedL1MessageBitmap: []byte{0x01, 0x02, 0x03},
|
||||
}
|
||||
|
||||
encoded := header.Encode()
|
||||
decoded, err := DecodeBatchHeader(encoded)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, header, decoded)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
abi "scroll-tech/bridge/abi"
|
||||
)
|
||||
|
||||
func TestBatchHash(t *testing.T) {
|
||||
txBytes := common.Hex2Bytes("02f8710582fd14808506e38dccc9825208944d496ccc28058b1d74b7a19541663e21154f9c848801561db11e24a43380c080a0d890606d7a35b2ab0f9b866d62c092d5b163f3e6a55537ae1485aac08c3f8ff7a023997be2d32f53e146b160fff0ba81e81dbb4491c865ab174d15c5b3d28c41ae")
|
||||
tx := new(gethTypes.Transaction)
|
||||
if err := tx.UnmarshalBinary(txBytes); err != nil {
|
||||
t.Fatalf("invalid tx hex string: %s", err)
|
||||
}
|
||||
|
||||
batchData := new(BatchData)
|
||||
batchData.TxHashes = append(batchData.TxHashes, tx.Hash())
|
||||
batchData.piCfg = &PublicInputHashConfig{
|
||||
MaxTxNum: 4,
|
||||
PaddingTxHash: common.HexToHash("0xb5baa665b2664c3bfed7eb46e00ebc110ecf2ebd257854a9bf2b9dbc9b2c08f6"),
|
||||
}
|
||||
|
||||
batch := &batchData.Batch
|
||||
batch.PrevStateRoot = common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000cafe")
|
||||
|
||||
block := abi.IScrollChainBlockContext{
|
||||
BlockNumber: 51966,
|
||||
Timestamp: 123456789,
|
||||
BaseFee: new(big.Int).SetUint64(0),
|
||||
GasLimit: 10000000000000000,
|
||||
NumTransactions: 1,
|
||||
NumL1Messages: 0,
|
||||
}
|
||||
batch.Blocks = append(batch.Blocks, block)
|
||||
|
||||
hash := batchData.Hash()
|
||||
assert.Equal(t, *hash, common.HexToHash("0xa9f2ca3175794f91226a410ba1e60fff07a405c957562675c4149b77e659d805"))
|
||||
|
||||
// use a different tx hash
|
||||
txBytes = common.Hex2Bytes("f8628001830f424094000000000000000000000000000000000000bbbb8080820a97a064e07cd8f939e2117724bdcbadc80dda421381cbc2a1f4e0d093d9cc5c5cf68ea03e264227f80852d88743cd9e43998f2746b619180366a87e4531debf9c3fa5dc")
|
||||
tx = new(gethTypes.Transaction)
|
||||
if err := tx.UnmarshalBinary(txBytes); err != nil {
|
||||
t.Fatalf("invalid tx hex string: %s", err)
|
||||
}
|
||||
batchData.TxHashes[0] = tx.Hash()
|
||||
|
||||
batchData.hash = nil // clear the cache
|
||||
assert.Equal(t, *batchData.Hash(), common.HexToHash("0x398cb22bbfa1665c1b342b813267538a4c933d7f92d8bd9184aba0dd1122987b"))
|
||||
}
|
||||
|
||||
func TestNewGenesisBatch(t *testing.T) {
|
||||
genesisBlock := &gethTypes.Header{
|
||||
UncleHash: common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"),
|
||||
Root: common.HexToHash("0x1b186a7a90ec3b41a2417062fe44dce8ce82ae76bfbb09eae786a4f1be1895f5"),
|
||||
TxHash: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"),
|
||||
ReceiptHash: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"),
|
||||
Difficulty: big.NewInt(1),
|
||||
Number: big.NewInt(0),
|
||||
GasLimit: 940000000,
|
||||
GasUsed: 0,
|
||||
Time: 1639724192,
|
||||
Extra: common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000214f8d488aa9ebf83e30bad45fb8f9c8ee2509f5511caff794753d07e9dfb218cfc233bb62d2c57022783094e1a7edb6f069f8424bb68496a0926b130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
BaseFee: big.NewInt(1000000000),
|
||||
}
|
||||
assert.Equal(
|
||||
t,
|
||||
genesisBlock.Hash().Hex(),
|
||||
"0x92826bd3aad2ef70d8061dc4e25150b305d1233d9cd7579433a77d6eb01dae1c",
|
||||
"wrong genesis block header",
|
||||
)
|
||||
|
||||
blockTrace := &WrappedBlock{genesisBlock, nil, common.Hash{}}
|
||||
batchData := NewGenesisBatchData(blockTrace)
|
||||
t.Log(batchData.Batch.Blocks[0])
|
||||
batchData.piCfg = &PublicInputHashConfig{
|
||||
MaxTxNum: 25,
|
||||
PaddingTxHash: common.HexToHash("0xb5baa665b2664c3bfed7eb46e00ebc110ecf2ebd257854a9bf2b9dbc9b2c08f6"),
|
||||
}
|
||||
assert.Equal(
|
||||
t,
|
||||
batchData.Hash().Hex(),
|
||||
"0x65cf210e30f75cf8fd198df124255f73bc08d6324759e828a784fa938e7ac43d",
|
||||
"wrong genesis batch hash",
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
const nonZeroByteGas uint64 = 16
|
||||
const zeroByteGas uint64 = 4
|
||||
|
||||
// WrappedBlock contains the block's Header, Transactions and WithdrawTrieRoot hash.
|
||||
type WrappedBlock struct {
|
||||
Header *types.Header `json:"header"`
|
||||
@@ -13,9 +21,116 @@ type WrappedBlock struct {
|
||||
WithdrawTrieRoot common.Hash `json:"withdraw_trie_root,omitempty"`
|
||||
}
|
||||
|
||||
// BatchInfo contains the BlockBatch's main info
|
||||
type BatchInfo struct {
|
||||
Index uint64 `json:"index"`
|
||||
Hash string `json:"hash"`
|
||||
StateRoot string `json:"state_root"`
|
||||
// NumL1Messages returns the number of L1 messages in this block.
|
||||
// This number is the sum of included and skipped L1 messages.
|
||||
func (w *WrappedBlock) NumL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
|
||||
var lastQueueIndex *uint64
|
||||
for _, txData := range w.Transactions {
|
||||
if txData.Type == types.L1MessageTxType {
|
||||
lastQueueIndex = &txData.Nonce
|
||||
}
|
||||
}
|
||||
if lastQueueIndex == nil {
|
||||
return 0
|
||||
}
|
||||
// note: last queue index included before this block is totalL1MessagePoppedBefore - 1
|
||||
// TODO: cache results
|
||||
return *lastQueueIndex - totalL1MessagePoppedBefore + 1
|
||||
}
|
||||
|
||||
// Encode encodes the WrappedBlock into RollupV2 BlockContext Encoding.
|
||||
func (w *WrappedBlock) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
|
||||
bytes := make([]byte, 60)
|
||||
|
||||
if !w.Header.Number.IsUint64() {
|
||||
return nil, errors.New("block number is not uint64")
|
||||
}
|
||||
if len(w.Transactions) > math.MaxUint16 {
|
||||
return nil, errors.New("number of transactions exceeds max uint16")
|
||||
}
|
||||
|
||||
numL1Messages := w.NumL1Messages(totalL1MessagePoppedBefore)
|
||||
if numL1Messages > math.MaxUint16 {
|
||||
return nil, errors.New("number of L1 messages exceeds max uint16")
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint64(bytes[0:], w.Header.Number.Uint64())
|
||||
binary.BigEndian.PutUint64(bytes[8:], w.Header.Time)
|
||||
// TODO: [16:47] Currently, baseFee is 0, because we disable EIP-1559.
|
||||
binary.BigEndian.PutUint64(bytes[48:], w.Header.GasLimit)
|
||||
binary.BigEndian.PutUint16(bytes[56:], uint16(len(w.Transactions)))
|
||||
binary.BigEndian.PutUint16(bytes[58:], uint16(numL1Messages))
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// EstimateL1CommitCalldataSize calculates the calldata size in l1 commit approximately.
|
||||
// TODO: The calculation could be more accurate by using 58 + len(l2TxDataBytes) (see Chunk).
|
||||
// This needs to be adjusted in the future.
|
||||
func (w *WrappedBlock) EstimateL1CommitCalldataSize() uint64 {
|
||||
var size uint64
|
||||
for _, txData := range w.Transactions {
|
||||
if txData.Type == types.L1MessageTxType {
|
||||
continue
|
||||
}
|
||||
size += uint64(len(txData.Data))
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// EstimateL1CommitGas calculates the calldata gas in l1 commit approximately.
|
||||
// TODO: This will need to be adjusted.
|
||||
// The part added here is only the calldata cost,
|
||||
// but we have execution cost for verifying blocks / chunks / batches and storing the batch hash.
|
||||
func (w *WrappedBlock) EstimateL1CommitGas() uint64 {
|
||||
var total uint64
|
||||
for _, txData := range w.Transactions {
|
||||
if txData.Type == types.L1MessageTxType {
|
||||
continue
|
||||
}
|
||||
data, _ := hexutil.Decode(txData.Data)
|
||||
tx := types.NewTx(&types.LegacyTx{
|
||||
Nonce: txData.Nonce,
|
||||
To: txData.To,
|
||||
Value: txData.Value.ToInt(),
|
||||
Gas: txData.Gas,
|
||||
GasPrice: txData.GasPrice.ToInt(),
|
||||
Data: data,
|
||||
V: txData.V.ToInt(),
|
||||
R: txData.R.ToInt(),
|
||||
S: txData.S.ToInt(),
|
||||
})
|
||||
rlpTxData, _ := tx.MarshalBinary()
|
||||
|
||||
for _, b := range rlpTxData {
|
||||
if b == 0 {
|
||||
total += zeroByteGas
|
||||
} else {
|
||||
total += nonZeroByteGas
|
||||
}
|
||||
}
|
||||
|
||||
var txLen [4]byte
|
||||
binary.BigEndian.PutUint32(txLen[:], uint32(len(rlpTxData)))
|
||||
|
||||
for _, b := range txLen {
|
||||
if b == 0 {
|
||||
total += zeroByteGas
|
||||
} else {
|
||||
total += nonZeroByteGas
|
||||
}
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// L2TxsNum calculates the number of l2 txs.
|
||||
func (w *WrappedBlock) L2TxsNum() uint64 {
|
||||
var count uint64
|
||||
for _, txData := range w.Transactions {
|
||||
if txData.Type != types.L1MessageTxType {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
137
bridge/internal/types/chunk.go
Normal file
137
bridge/internal/types/chunk.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// Chunk contains blocks to be encoded
|
||||
type Chunk struct {
|
||||
Blocks []*WrappedBlock `json:"blocks"`
|
||||
}
|
||||
|
||||
// NumL1Messages returns the number of L1 messages in this chunk.
|
||||
// This number is the sum of included and skipped L1 messages.
|
||||
func (c *Chunk) NumL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
|
||||
var numL1Messages uint64
|
||||
for _, block := range c.Blocks {
|
||||
numL1MessagesInBlock := block.NumL1Messages(totalL1MessagePoppedBefore)
|
||||
numL1Messages += numL1MessagesInBlock
|
||||
totalL1MessagePoppedBefore += numL1MessagesInBlock
|
||||
}
|
||||
// TODO: cache results
|
||||
return numL1Messages
|
||||
}
|
||||
|
||||
// Encode encodes the Chunk into RollupV2 Chunk Encoding.
|
||||
func (c *Chunk) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
|
||||
numBlocks := len(c.Blocks)
|
||||
|
||||
if numBlocks > 255 {
|
||||
return nil, errors.New("number of blocks exceeds 1 byte")
|
||||
}
|
||||
if numBlocks == 0 {
|
||||
return nil, errors.New("number of blocks is 0")
|
||||
}
|
||||
|
||||
var chunkBytes []byte
|
||||
chunkBytes = append(chunkBytes, byte(numBlocks))
|
||||
|
||||
var l2TxDataBytes []byte
|
||||
|
||||
for _, block := range c.Blocks {
|
||||
blockBytes, err := block.Encode(totalL1MessagePoppedBefore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode block: %v", err)
|
||||
}
|
||||
totalL1MessagePoppedBefore += block.NumL1Messages(totalL1MessagePoppedBefore)
|
||||
|
||||
if len(blockBytes) != 60 {
|
||||
return nil, fmt.Errorf("block encoding is not 60 bytes long %x", len(blockBytes))
|
||||
}
|
||||
|
||||
chunkBytes = append(chunkBytes, blockBytes...)
|
||||
|
||||
// Append rlp-encoded l2Txs
|
||||
for _, txData := range block.Transactions {
|
||||
if txData.Type == types.L1MessageTxType {
|
||||
continue
|
||||
}
|
||||
data, err := hexutil.Decode(txData.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// right now we only support legacy tx
|
||||
tx := types.NewTx(&types.LegacyTx{
|
||||
Nonce: txData.Nonce,
|
||||
To: txData.To,
|
||||
Value: txData.Value.ToInt(),
|
||||
Gas: txData.Gas,
|
||||
GasPrice: txData.GasPrice.ToInt(),
|
||||
Data: data,
|
||||
V: txData.V.ToInt(),
|
||||
R: txData.R.ToInt(),
|
||||
S: txData.S.ToInt(),
|
||||
})
|
||||
rlpTxData, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var txLen [4]byte
|
||||
binary.BigEndian.PutUint32(txLen[:], uint32(len(rlpTxData)))
|
||||
l2TxDataBytes = append(l2TxDataBytes, txLen[:]...)
|
||||
l2TxDataBytes = append(l2TxDataBytes, rlpTxData...)
|
||||
}
|
||||
}
|
||||
|
||||
chunkBytes = append(chunkBytes, l2TxDataBytes...)
|
||||
|
||||
return chunkBytes, nil
|
||||
}
|
||||
|
||||
// Hash hashes the Chunk into RollupV2 Chunk Hash
|
||||
func (c *Chunk) Hash(totalL1MessagePoppedBefore uint64) (common.Hash, error) {
|
||||
chunkBytes, err := c.Encode(totalL1MessagePoppedBefore)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
numBlocks := chunkBytes[0]
|
||||
|
||||
// concatenate block contexts
|
||||
var dataBytes []byte
|
||||
for i := 0; i < int(numBlocks); i++ {
|
||||
// only the first 58 bytes of each BlockContext are needed for the hashing process
|
||||
dataBytes = append(dataBytes, chunkBytes[1+60*i:60*i+59]...)
|
||||
}
|
||||
|
||||
// concatenate l1 and l2 tx hashes
|
||||
for _, block := range c.Blocks {
|
||||
var l1TxHashes []byte
|
||||
var l2TxHashes []byte
|
||||
for _, txData := range block.Transactions {
|
||||
txHash := strings.TrimPrefix(txData.TxHash, "0x")
|
||||
hashBytes, err := hex.DecodeString(txHash)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
if txData.Type == types.L1MessageTxType {
|
||||
l1TxHashes = append(l1TxHashes, hashBytes...)
|
||||
} else {
|
||||
l2TxHashes = append(l2TxHashes, hashBytes...)
|
||||
}
|
||||
}
|
||||
dataBytes = append(dataBytes, l1TxHashes...)
|
||||
dataBytes = append(dataBytes, l2TxHashes...)
|
||||
}
|
||||
|
||||
hash := crypto.Keccak256Hash(dataBytes)
|
||||
return hash, nil
|
||||
}
|
||||
137
bridge/internal/types/chunk_test.go
Normal file
137
bridge/internal/types/chunk_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestChunkEncode(t *testing.T) {
|
||||
// Test case 1: when the chunk contains no blocks.
|
||||
chunk := &Chunk{
|
||||
Blocks: []*WrappedBlock{},
|
||||
}
|
||||
bytes, err := chunk.Encode(0)
|
||||
assert.Nil(t, bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "number of blocks is 0")
|
||||
|
||||
// Test case 2: when the chunk contains more than 255 blocks.
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{},
|
||||
}
|
||||
for i := 0; i < 256; i++ {
|
||||
chunk.Blocks = append(chunk.Blocks, &WrappedBlock{})
|
||||
}
|
||||
bytes, err = chunk.Encode(0)
|
||||
assert.Nil(t, bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "number of blocks exceeds 1 byte")
|
||||
|
||||
// Test case 3: when the chunk contains one block.
|
||||
templateBlockTrace, err := os.ReadFile("../../../common/testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace, wrappedBlock))
|
||||
assert.Equal(t, uint64(0), wrappedBlock.NumL1Messages(0))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock,
|
||||
},
|
||||
}
|
||||
bytes, err = chunk.Encode(0)
|
||||
hexString := hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 299, len(bytes))
|
||||
assert.Equal(t, "0100000000000000020000000063807b2a0000000000000000000000000000000000000000000000000000000000000000000355418d1e81840002000000000073f87180843b9aec2e8307a12094c0c4c8baea3f6acb49b6e1fb9e2adeceeacb0ca28a152d02c7e14af60000008083019ecea0ab07ae99c67aa78e7ba5cf6781e90cc32b219b1de102513d56548a41e86df514a034cbd19feacd73e8ce64d00c4d1996b9b5243c578fd7f51bfaec288bbaf42a8b00000073f87101843b9aec2e8307a1209401bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed8a152d02c7e14af60000008083019ecea0f039985866d8256f10c1be4f7b2cace28d8f20bde27e2604393eb095b7f77316a05a3e6e81065f2b4604bcec5bd4aba684835996fc3f879380aac1c09c6eed32f1", hexString)
|
||||
|
||||
// Test case 4: when the chunk contains one block with 1 L1MsgTx
|
||||
templateBlockTrace2, err := os.ReadFile("../../../common/testdata/blockTrace_04.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
wrappedBlock2 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace2, wrappedBlock2))
|
||||
assert.Equal(t, uint64(11), wrappedBlock2.NumL1Messages(0)) // 0..=9 skipped, 10 included
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
bytes, err = chunk.Encode(0)
|
||||
hexString = hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 97, len(bytes))
|
||||
assert.Equal(t, "01000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000b00000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
|
||||
|
||||
// Test case 5: when the chunk contains two blocks each with 1 L1MsgTx
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock2,
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
bytes, err = chunk.Encode(0)
|
||||
hexString = hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 193, len(bytes))
|
||||
assert.Equal(t, "02000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000b000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e105808080808000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
|
||||
}
|
||||
|
||||
func TestChunkHash(t *testing.T) {
|
||||
// Test case 1: when the chunk contains no blocks
|
||||
chunk := &Chunk{
|
||||
Blocks: []*WrappedBlock{},
|
||||
}
|
||||
hash, err := chunk.Hash(0)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", hash.Hex())
|
||||
assert.Contains(t, err.Error(), "number of blocks is 0")
|
||||
|
||||
// Test case 2: successfully hashing a chunk on one block
|
||||
templateBlockTrace, err := os.ReadFile("../../../common/testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
wrappedBlock := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace, wrappedBlock))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock,
|
||||
},
|
||||
}
|
||||
hash, err = chunk.Hash(0)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0x78c839dfc494396c16b40946f32b3f4c3e8c2d4bfd04aefcf235edec474482f8", hash.Hex())
|
||||
|
||||
// Test case 3: successfully hashing a chunk on two blocks
|
||||
templateBlockTrace1, err := os.ReadFile("../../../common/testdata/blockTrace_03.json")
|
||||
assert.NoError(t, err)
|
||||
wrappedBlock1 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace1, wrappedBlock1))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock,
|
||||
wrappedBlock1,
|
||||
},
|
||||
}
|
||||
hash, err = chunk.Hash(0)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0xaa9e494f72bc6965857856f0fae6916f27b2a6591c714a573b2fab46df03b8ae", hash.Hex())
|
||||
|
||||
// Test case 4: successfully hashing a chunk on two blocks each with L1 and L2 txs
|
||||
templateBlockTrace2, err := os.ReadFile("../../../common/testdata/blockTrace_04.json")
|
||||
assert.NoError(t, err)
|
||||
wrappedBlock2 := &WrappedBlock{}
|
||||
assert.NoError(t, json.Unmarshal(templateBlockTrace2, wrappedBlock2))
|
||||
chunk = &Chunk{
|
||||
Blocks: []*WrappedBlock{
|
||||
wrappedBlock2,
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
hash, err = chunk.Hash(0)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0x42967825696a129e7a83f082097aca982747480956dcaa448c9296e795c9a91a", hash.Hex())
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {BatchHeaderV0Codec} from "../../contracts/src/libraries/codec/BatchHeaderV0Codec.sol";
|
||||
import {ChunkCodec} from "../../contracts/src/libraries/codec/ChunkCodec.sol";
|
||||
import {IL1MessageQueue} from "../../contracts/src/L1/rollup/IL1MessageQueue.sol";
|
||||
|
||||
contract MockBridgeL1 {
|
||||
/******************************
|
||||
* Events from L1MessageQueue *
|
||||
@@ -17,7 +21,7 @@ contract MockBridgeL1 {
|
||||
address indexed sender,
|
||||
address indexed target,
|
||||
uint256 value,
|
||||
uint256 queueIndex,
|
||||
uint64 queueIndex,
|
||||
uint256 gasLimit,
|
||||
bytes data
|
||||
);
|
||||
@@ -46,74 +50,27 @@ contract MockBridgeL1 {
|
||||
/// @param messageHash The hash of the message.
|
||||
event RelayedMessage(bytes32 indexed messageHash);
|
||||
|
||||
/// @dev The maximum number of transaction in on batch.
|
||||
uint256 public immutable maxNumTxInBatch;
|
||||
|
||||
/// @dev The hash used for padding public inputs.
|
||||
bytes32 public immutable paddingTxHash;
|
||||
|
||||
/***************************
|
||||
* Events from ScrollChain *
|
||||
***************************/
|
||||
|
||||
/// @notice Emitted when a new batch is commited.
|
||||
/// @param batchHash The hash of the batch
|
||||
/// @notice Emitted when a new batch is committed.
|
||||
/// @param batchHash The hash of the batch.
|
||||
event CommitBatch(bytes32 indexed batchHash);
|
||||
|
||||
/// @notice Emitted when a batch is reverted.
|
||||
/// @param batchHash The identification of the batch.
|
||||
event RevertBatch(bytes32 indexed batchHash);
|
||||
|
||||
/// @notice Emitted when a batch is finalized.
|
||||
/// @param batchHash The hash of the batch
|
||||
event FinalizeBatch(bytes32 indexed batchHash);
|
||||
/// @param stateRoot The state root in layer 2 after this batch.
|
||||
/// @param withdrawRoot The merkle root in layer2 after this batch.
|
||||
event FinalizeBatch(bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot);
|
||||
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
|
||||
struct BlockContext {
|
||||
// The hash of this block.
|
||||
bytes32 blockHash;
|
||||
// The parent hash of this block.
|
||||
bytes32 parentHash;
|
||||
// The height of this block.
|
||||
uint64 blockNumber;
|
||||
// The timestamp of this block.
|
||||
uint64 timestamp;
|
||||
// The base fee of this block.
|
||||
// Currently, it is not used, because we disable EIP-1559.
|
||||
// We keep it for future proof.
|
||||
uint256 baseFee;
|
||||
// The gas limit of this block.
|
||||
uint64 gasLimit;
|
||||
// The number of transactions in this block, both L1 & L2 txs.
|
||||
uint16 numTransactions;
|
||||
// The number of l1 messages in this block.
|
||||
uint16 numL1Messages;
|
||||
}
|
||||
|
||||
struct Batch {
|
||||
// The list of blocks in this batch
|
||||
BlockContext[] blocks; // MAX_NUM_BLOCKS = 100, about 5 min
|
||||
// The state root of previous batch.
|
||||
// The first batch will use 0x0 for prevStateRoot
|
||||
bytes32 prevStateRoot;
|
||||
// The state root of the last block in this batch.
|
||||
bytes32 newStateRoot;
|
||||
// The withdraw trie root of the last block in this batch.
|
||||
bytes32 withdrawTrieRoot;
|
||||
// The index of the batch.
|
||||
uint64 batchIndex;
|
||||
// The parent batch hash.
|
||||
bytes32 parentBatchHash;
|
||||
// Concatenated raw data of RLP encoded L2 txs
|
||||
bytes l2Transactions;
|
||||
}
|
||||
|
||||
struct L2MessageProof {
|
||||
// The hash of the batch where the message belongs to.
|
||||
bytes32 batchHash;
|
||||
// The index of the batch where the message belongs to.
|
||||
uint256 batchIndex;
|
||||
// Concatenation of merkle proof for withdraw merkle trie.
|
||||
bytes merkleProof;
|
||||
}
|
||||
@@ -125,14 +82,7 @@ contract MockBridgeL1 {
|
||||
/// @notice Message nonce, used to avoid relay attack.
|
||||
uint256 public messageNonce;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor() {
|
||||
maxNumTxInBatch = 44;
|
||||
paddingTxHash = 0x0000000000000000000000000000000000000000000000000000000000000000;
|
||||
}
|
||||
mapping(uint256 => bytes32) public committedBatches;
|
||||
|
||||
/***********************************
|
||||
* Functions from L2GasPriceOracle *
|
||||
@@ -154,7 +104,7 @@ contract MockBridgeL1 {
|
||||
bytes memory _xDomainCalldata = _encodeXDomainCalldata(msg.sender, target, value, messageNonce, message);
|
||||
{
|
||||
address _sender = applyL1ToL2Alias(address(this));
|
||||
emit QueueTransaction(_sender, target, 0, messageNonce, gasLimit, _xDomainCalldata);
|
||||
emit QueueTransaction(_sender, target, 0, uint64(messageNonce), gasLimit, _xDomainCalldata);
|
||||
}
|
||||
|
||||
emit SentMessage(msg.sender, target, value, messageNonce, gasLimit, message);
|
||||
@@ -178,37 +128,65 @@ contract MockBridgeL1 {
|
||||
* Functions from ScrollChain *
|
||||
******************************/
|
||||
|
||||
function commitBatch(Batch memory _batch) external {
|
||||
_commitBatch(_batch);
|
||||
}
|
||||
function commitBatch(
|
||||
uint8 /*version*/,
|
||||
bytes calldata /*parentBatchHeader*/,
|
||||
bytes[] memory chunks,
|
||||
bytes calldata /*skippedL1MessageBitmap*/
|
||||
) external {
|
||||
// check whether the batch is empty
|
||||
uint256 _chunksLength = chunks.length;
|
||||
require(_chunksLength > 0, "batch is empty");
|
||||
|
||||
function commitBatches(Batch[] memory _batches) external {
|
||||
for (uint256 i = 0; i < _batches.length; i++) {
|
||||
_commitBatch(_batches[i]);
|
||||
uint256 dataPtr;
|
||||
assembly {
|
||||
dataPtr := mload(0x40)
|
||||
mstore(0x40, add(dataPtr, mul(_chunksLength, 32)))
|
||||
}
|
||||
}
|
||||
|
||||
function revertBatch(bytes32 _batchHash) external {
|
||||
emit RevertBatch(_batchHash);
|
||||
for (uint256 i = 0; i < _chunksLength; i++) {
|
||||
_commitChunk(dataPtr, chunks[i]);
|
||||
|
||||
unchecked {
|
||||
dataPtr += 32;
|
||||
}
|
||||
}
|
||||
|
||||
bytes32 _dataHash;
|
||||
assembly {
|
||||
let dataLen := mul(_chunksLength, 0x20)
|
||||
_dataHash := keccak256(sub(dataPtr, dataLen), dataLen)
|
||||
}
|
||||
|
||||
bytes memory paddedData = new bytes(89);
|
||||
assembly {
|
||||
mstore(add(paddedData, 57), _dataHash)
|
||||
}
|
||||
|
||||
uint256 batchPtr;
|
||||
assembly {
|
||||
batchPtr := add(paddedData, 32)
|
||||
}
|
||||
bytes32 _batchHash = BatchHeaderV0Codec.computeBatchHash(batchPtr, 89);
|
||||
committedBatches[0] = _batchHash;
|
||||
emit CommitBatch(_batchHash);
|
||||
}
|
||||
|
||||
function finalizeBatchWithProof(
|
||||
bytes32 _batchHash,
|
||||
uint256[] memory,
|
||||
uint256[] memory
|
||||
bytes calldata /*batchHeader*/,
|
||||
bytes32 /*prevStateRoot*/,
|
||||
bytes32 postStateRoot,
|
||||
bytes32 withdrawRoot,
|
||||
bytes calldata /*aggrProof*/
|
||||
) external {
|
||||
emit FinalizeBatch(_batchHash);
|
||||
bytes32 _batchHash = committedBatches[0];
|
||||
emit FinalizeBatch(_batchHash, postStateRoot, withdrawRoot);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
function _commitBatch(Batch memory _batch) internal {
|
||||
bytes32 _batchHash = _computePublicInputHash(_batch);
|
||||
emit CommitBatch(_batchHash);
|
||||
}
|
||||
|
||||
/// @dev Internal function to generate the correct cross domain calldata for a message.
|
||||
/// @param _sender Message sender address.
|
||||
/// @param _target Target contract address.
|
||||
@@ -234,6 +212,10 @@ contract MockBridgeL1 {
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Utility function that converts the address in the L1 that submitted a tx to
|
||||
/// the inbox to the msg.sender viewed in the L2
|
||||
/// @param l1Address the address in the L1 that triggered the tx to L2
|
||||
/// @return l2Address L2 address as viewed in msg.sender
|
||||
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
|
||||
uint160 offset = uint160(0x1111000000000000000000000000000000001111);
|
||||
unchecked {
|
||||
@@ -241,140 +223,67 @@ contract MockBridgeL1 {
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Internal function to compute the public input hash.
|
||||
/// @param batch The batch to compute.
|
||||
function _computePublicInputHash(Batch memory batch)
|
||||
internal
|
||||
view
|
||||
returns (
|
||||
bytes32
|
||||
)
|
||||
{
|
||||
uint256 publicInputsPtr;
|
||||
// 1. append prevStateRoot, newStateRoot and withdrawTrieRoot to public inputs
|
||||
{
|
||||
bytes32 prevStateRoot = batch.prevStateRoot;
|
||||
bytes32 newStateRoot = batch.newStateRoot;
|
||||
bytes32 withdrawTrieRoot = batch.withdrawTrieRoot;
|
||||
// number of bytes in public inputs: 32 * 3 + 124 * blocks + 32 * MAX_NUM_TXS
|
||||
uint256 publicInputsSize = 32 * 3 + batch.blocks.length * 124 + 32 * maxNumTxInBatch;
|
||||
assembly {
|
||||
publicInputsPtr := mload(0x40)
|
||||
mstore(0x40, add(publicInputsPtr, publicInputsSize))
|
||||
mstore(publicInputsPtr, prevStateRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, newStateRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, withdrawTrieRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
function _commitChunk(
|
||||
uint256 memPtr,
|
||||
bytes memory _chunk
|
||||
) internal pure {
|
||||
uint256 chunkPtr;
|
||||
uint256 startDataPtr;
|
||||
uint256 dataPtr;
|
||||
uint256 blockPtr;
|
||||
|
||||
assembly {
|
||||
dataPtr := mload(0x40)
|
||||
startDataPtr := dataPtr
|
||||
chunkPtr := add(_chunk, 0x20) // skip chunkLength
|
||||
blockPtr := add(chunkPtr, 1) // skip numBlocks
|
||||
}
|
||||
|
||||
uint256 _numBlocks = ChunkCodec.validateChunkLength(chunkPtr, _chunk.length);
|
||||
|
||||
// concatenate block contexts
|
||||
uint256 _totalTransactionsInChunk;
|
||||
for (uint256 i = 0; i < _numBlocks; i++) {
|
||||
dataPtr = ChunkCodec.copyBlockContext(chunkPtr, dataPtr, i);
|
||||
uint256 _numTransactionsInBlock = ChunkCodec.numTransactions(blockPtr);
|
||||
unchecked {
|
||||
_totalTransactionsInChunk += _numTransactionsInBlock;
|
||||
blockPtr += ChunkCodec.BLOCK_CONTEXT_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
uint64 numTransactionsInBatch;
|
||||
BlockContext memory _block;
|
||||
// 2. append block information to public inputs.
|
||||
for (uint256 i = 0; i < batch.blocks.length; i++) {
|
||||
// validate blocks, we won't check first block against previous batch.
|
||||
{
|
||||
BlockContext memory _currentBlock = batch.blocks[i];
|
||||
if (i > 0) {
|
||||
require(_block.blockHash == _currentBlock.parentHash, "Parent hash mismatch");
|
||||
require(_block.blockNumber + 1 == _currentBlock.blockNumber, "Block number mismatch");
|
||||
}
|
||||
_block = _currentBlock;
|
||||
}
|
||||
assembly {
|
||||
mstore(0x40, add(dataPtr, mul(_totalTransactionsInChunk, 0x20))) // reserve memory for tx hashes
|
||||
blockPtr := add(chunkPtr, 1) // reset block ptr
|
||||
}
|
||||
|
||||
// append blockHash and parentHash to public inputs
|
||||
{
|
||||
bytes32 blockHash = _block.blockHash;
|
||||
bytes32 parentHash = _block.parentHash;
|
||||
// concatenate tx hashes
|
||||
uint256 l2TxPtr = ChunkCodec.l2TxPtr(chunkPtr, _numBlocks);
|
||||
while (_numBlocks > 0) {
|
||||
// concatenate l2 transaction hashes
|
||||
uint256 _numTransactionsInBlock = ChunkCodec.numTransactions(blockPtr);
|
||||
for (uint256 j = 0; j < _numTransactionsInBlock; j++) {
|
||||
bytes32 txHash;
|
||||
(txHash, l2TxPtr) = ChunkCodec.loadL2TxHash(l2TxPtr);
|
||||
assembly {
|
||||
mstore(publicInputsPtr, blockHash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, parentHash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(dataPtr, txHash)
|
||||
dataPtr := add(dataPtr, 0x20)
|
||||
}
|
||||
}
|
||||
// append blockNumber and blockTimestamp to public inputs
|
||||
{
|
||||
uint256 blockNumber = _block.blockNumber;
|
||||
uint256 blockTimestamp = _block.timestamp;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, shl(192, blockNumber))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
mstore(publicInputsPtr, shl(192, blockTimestamp))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
}
|
||||
}
|
||||
// append baseFee to public inputs
|
||||
{
|
||||
uint256 baseFee = _block.baseFee;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, baseFee)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
uint64 numTransactionsInBlock = _block.numTransactions;
|
||||
// gasLimit, numTransactions and numL1Messages to public inputs
|
||||
{
|
||||
uint256 gasLimit = _block.gasLimit;
|
||||
uint256 numL1MessagesInBlock = _block.numL1Messages;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, shl(192, gasLimit))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
mstore(publicInputsPtr, shl(240, numTransactionsInBlock))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x2)
|
||||
mstore(publicInputsPtr, shl(240, numL1MessagesInBlock))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x2)
|
||||
}
|
||||
}
|
||||
numTransactionsInBatch += numTransactionsInBlock;
|
||||
}
|
||||
require(numTransactionsInBatch <= maxNumTxInBatch, "Too many transactions in batch");
|
||||
|
||||
// 3. append transaction hash to public inputs.
|
||||
uint256 _l2TxnPtr;
|
||||
{
|
||||
bytes memory l2Transactions = batch.l2Transactions;
|
||||
assembly {
|
||||
_l2TxnPtr := add(l2Transactions, 0x20)
|
||||
}
|
||||
}
|
||||
for (uint256 i = 0; i < batch.blocks.length; i++) {
|
||||
uint256 numL1MessagesInBlock = batch.blocks[i].numL1Messages;
|
||||
require(numL1MessagesInBlock == 0);
|
||||
uint256 numTransactionsInBlock = batch.blocks[i].numTransactions;
|
||||
for (uint256 j = numL1MessagesInBlock; j < numTransactionsInBlock; ++j) {
|
||||
bytes32 hash;
|
||||
assembly {
|
||||
let txPayloadLength := shr(224, mload(_l2TxnPtr))
|
||||
_l2TxnPtr := add(_l2TxnPtr, 4)
|
||||
_l2TxnPtr := add(_l2TxnPtr, txPayloadLength)
|
||||
hash := keccak256(sub(_l2TxnPtr, txPayloadLength), txPayloadLength)
|
||||
mstore(publicInputsPtr, hash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
unchecked {
|
||||
_numBlocks -= 1;
|
||||
blockPtr += ChunkCodec.BLOCK_CONTEXT_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. append padding transaction to public inputs.
|
||||
bytes32 txHashPadding = paddingTxHash;
|
||||
for (uint256 i = numTransactionsInBatch; i < maxNumTxInBatch; i++) {
|
||||
assembly {
|
||||
mstore(publicInputsPtr, txHashPadding)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
// check chunk has correct length
|
||||
require(l2TxPtr - chunkPtr == _chunk.length, "incomplete l2 transaction data");
|
||||
|
||||
// 5. compute public input hash
|
||||
bytes32 publicInputHash;
|
||||
{
|
||||
uint256 publicInputsSize = 32 * 3 + batch.blocks.length * 124 + 32 * maxNumTxInBatch;
|
||||
assembly {
|
||||
publicInputHash := keccak256(sub(publicInputsPtr, publicInputsSize), publicInputsSize)
|
||||
}
|
||||
// compute data hash and store to memory
|
||||
assembly {
|
||||
let dataHash := keccak256(startDataPtr, sub(dataPtr, startDataPtr))
|
||||
mstore(memPtr, dataHash)
|
||||
}
|
||||
|
||||
return publicInputHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,6 @@ contract MockBridgeL2 {
|
||||
/// @param messageHash The hash of the corresponding message.
|
||||
event AppendMessage(uint256 index, bytes32 messageHash);
|
||||
|
||||
/********************************
|
||||
* Events from L1BlockContainer *
|
||||
********************************/
|
||||
|
||||
/// @notice Emitted when a block is imported.
|
||||
/// @param blockHash The hash of the imported block.
|
||||
/// @param blockHeight The height of the imported block.
|
||||
/// @param blockTimestamp The timestamp of the imported block.
|
||||
/// @param baseFee The base fee of the imported block.
|
||||
/// @param stateRoot The state root of the imported block.
|
||||
event ImportBlock(
|
||||
bytes32 indexed blockHash,
|
||||
uint256 blockHeight,
|
||||
uint256 blockTimestamp,
|
||||
uint256 baseFee,
|
||||
bytes32 stateRoot
|
||||
);
|
||||
|
||||
/*********************************
|
||||
* Events from L2ScrollMessenger *
|
||||
*********************************/
|
||||
|
||||
@@ -63,9 +63,9 @@ func setupDB(t *testing.T) *gorm.DB {
|
||||
func TestMain(m *testing.M) {
|
||||
base = docker.NewDockerApp()
|
||||
bridgeApp = bcmd.NewBridgeApp(base, "../conf/config.json")
|
||||
defer bridgeApp.Free()
|
||||
defer base.Free()
|
||||
m.Run()
|
||||
bridgeApp.Free()
|
||||
base.Free()
|
||||
}
|
||||
|
||||
func setupEnv(t *testing.T) {
|
||||
@@ -129,6 +129,10 @@ func prepareContracts(t *testing.T) {
|
||||
func TestFunction(t *testing.T) {
|
||||
setupEnv(t)
|
||||
|
||||
// process start test
|
||||
t.Run("TestProcessStart", testProcessStart)
|
||||
t.Run("TestProcessStartEnableMetrics", testProcessStartEnableMetrics)
|
||||
|
||||
// l1 rollup and watch rollup events
|
||||
t.Run("TestCommitBatchAndFinalizeBatch", testCommitBatchAndFinalizeBatch)
|
||||
|
||||
@@ -136,7 +140,7 @@ func TestFunction(t *testing.T) {
|
||||
t.Run("TestRelayL1MessageSucceed", testRelayL1MessageSucceed)
|
||||
|
||||
// l2 message
|
||||
t.Run("TestRelayL2MessageSucceed", testRelayL2MessageSucceed)
|
||||
// TODO: add a "user relay l2msg Succeed" test
|
||||
|
||||
// l1/l2 gas oracle
|
||||
t.Run("TestImportL1GasPrice", testImportL1GasPrice)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
|
||||
@@ -69,50 +68,40 @@ func testImportL2GasPrice(t *testing.T) {
|
||||
prepareContracts(t)
|
||||
|
||||
l2Cfg := bridgeApp.Config.L2Config
|
||||
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
|
||||
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add fake blocks
|
||||
traces := []*bridgeTypes.WrappedBlock{
|
||||
{
|
||||
Header: &gethTypes.Header{
|
||||
Number: big.NewInt(1),
|
||||
ParentHash: common.Hash{},
|
||||
Difficulty: big.NewInt(0),
|
||||
BaseFee: big.NewInt(0),
|
||||
// add fake chunk
|
||||
chunk := &bridgeTypes.Chunk{
|
||||
Blocks: []*bridgeTypes.WrappedBlock{
|
||||
{
|
||||
Header: &gethTypes.Header{
|
||||
Number: big.NewInt(1),
|
||||
ParentHash: common.Hash{},
|
||||
Difficulty: big.NewInt(0),
|
||||
BaseFee: big.NewInt(0),
|
||||
},
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
},
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
},
|
||||
}
|
||||
chunkHash, err := chunk.Hash(0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blockTraceOrm := orm.NewBlockTrace(db)
|
||||
assert.NoError(t, blockTraceOrm.InsertWrappedBlocks(traces))
|
||||
|
||||
parentBatch := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: "0x0000000000000000000000000000000000000000",
|
||||
}
|
||||
batchData := bridgeTypes.NewBatchData(parentBatch, []*bridgeTypes.WrappedBlock{traces[0]}, l2Cfg.BatchProposerConfig.PublicInputConfig)
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
_, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
batchOrm := orm.NewBatch(db)
|
||||
_, err = batchOrm.InsertBatch(context.Background(), 0, 0, chunkHash.Hex(), chunkHash.Hex(), []*bridgeTypes.Chunk{chunk})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check db status
|
||||
batch, err := blockBatchOrm.GetLatestBatch()
|
||||
batch, err := batchOrm.GetLatestBatch(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, batch.OracleTxHash)
|
||||
assert.Equal(t, types.GasOracleStatus(batch.OracleStatus), types.GasOraclePending)
|
||||
|
||||
// relay gas price
|
||||
l2Relayer.ProcessGasPriceOracle()
|
||||
batch, err = blockBatchOrm.GetLatestBatch()
|
||||
batch, err = batchOrm.GetLatestBatch(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, batch.OracleTxHash)
|
||||
assert.Equal(t, types.GasOracleStatus(batch.OracleStatus), types.GasOracleImporting)
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi/bind"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
|
||||
"scroll-tech/bridge/internal/controller/relayer"
|
||||
"scroll-tech/bridge/internal/controller/watcher"
|
||||
"scroll-tech/bridge/internal/orm"
|
||||
bridgeTypes "scroll-tech/bridge/internal/types"
|
||||
"scroll-tech/bridge/internal/utils"
|
||||
)
|
||||
|
||||
func testRelayL2MessageSucceed(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
defer utils.CloseDB(db)
|
||||
|
||||
prepareContracts(t)
|
||||
|
||||
l2Cfg := bridgeApp.Config.L2Config
|
||||
|
||||
// Create L2Watcher
|
||||
confirmations := rpc.LatestBlockNumber
|
||||
l2Watcher := watcher.NewL2WatcherClient(context.Background(), l2Client, confirmations, l2Cfg.L2MessengerAddress, l2Cfg.L2MessageQueueAddress, l2Cfg.WithdrawTrieRootSlot, db)
|
||||
|
||||
// Create L2Relayer
|
||||
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create L1Watcher
|
||||
l1Cfg := bridgeApp.Config.L1Config
|
||||
l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, 0, confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
|
||||
|
||||
// send message through l2 messenger contract
|
||||
nonce, err := l2MessengerInstance.MessageNonce(&bind.CallOpts{})
|
||||
assert.NoError(t, err)
|
||||
sendTx, err := l2MessengerInstance.SendMessage(l2Auth, l1Auth.From, big.NewInt(0), common.Hex2Bytes("00112233"), big.NewInt(0))
|
||||
assert.NoError(t, err)
|
||||
sendReceipt, err := bind.WaitMined(context.Background(), l2Client, sendTx)
|
||||
assert.NoError(t, err)
|
||||
if sendReceipt.Status != gethTypes.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
|
||||
// l2 watch process events
|
||||
l2Watcher.FetchContractEvent()
|
||||
l2MessageOrm := orm.NewL2Message(db)
|
||||
blockTraceOrm := orm.NewBlockTrace(db)
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
|
||||
// check db status
|
||||
msg, err := l2MessageOrm.GetL2MessageByNonce(nonce.Uint64())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.MsgStatus(msg.Status), types.MsgPending)
|
||||
assert.Equal(t, msg.Sender, l2Auth.From.String())
|
||||
assert.Equal(t, msg.Target, l1Auth.From.String())
|
||||
|
||||
// add fake blocks
|
||||
traces := []*bridgeTypes.WrappedBlock{
|
||||
{
|
||||
Header: &gethTypes.Header{
|
||||
Number: sendReceipt.BlockNumber,
|
||||
ParentHash: common.Hash{},
|
||||
Difficulty: big.NewInt(0),
|
||||
BaseFee: big.NewInt(0),
|
||||
},
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
},
|
||||
}
|
||||
assert.NoError(t, blockTraceOrm.InsertWrappedBlocks(traces))
|
||||
|
||||
parentBatch := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: "0x0000000000000000000000000000000000000000",
|
||||
}
|
||||
batchData := bridgeTypes.NewBatchData(parentBatch, []*bridgeTypes.WrappedBlock{traces[0]}, l2Cfg.BatchProposerConfig.PublicInputConfig)
|
||||
batchHash := batchData.Hash().String()
|
||||
// add fake batch
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
rowsAffected, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
var blockIDs = make([]uint64, len(batchData.Batch.Blocks))
|
||||
for i, block := range batchData.Batch.Blocks {
|
||||
blockIDs[i] = block.BlockNumber
|
||||
}
|
||||
dbTxErr = blockTraceOrm.UpdateBatchHashForL2Blocks(tx, blockIDs, batchHash)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add dummy proof
|
||||
tProof := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
tInstanceCommitments := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
err = blockBatchOrm.UpdateProofByHash(context.Background(), batchHash, tProof, tInstanceCommitments, 100)
|
||||
assert.NoError(t, err)
|
||||
err = blockBatchOrm.UpdateProvingStatus(batchHash, types.ProvingTaskVerified)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// process pending batch and check status
|
||||
assert.NoError(t, l2Relayer.SendCommitTx([]*bridgeTypes.BatchData{batchData}))
|
||||
|
||||
blockBatches, err := blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batchHash}, nil, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(blockBatches))
|
||||
assert.NotEmpty(t, blockBatches[0].CommitTxHash)
|
||||
assert.Equal(t, types.RollupCommitting, types.RollupStatus(blockBatches[0].RollupStatus))
|
||||
|
||||
commitTx, _, err := l1Client.TransactionByHash(context.Background(), common.HexToHash(blockBatches[0].CommitTxHash))
|
||||
assert.NoError(t, err)
|
||||
commitTxReceipt, err := bind.WaitMined(context.Background(), l1Client, commitTx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(commitTxReceipt.Logs), 1)
|
||||
|
||||
// fetch CommitBatch rollup events
|
||||
err = l1Watcher.FetchContractEvent()
|
||||
assert.NoError(t, err)
|
||||
statuses, err := blockBatchOrm.GetRollupStatusByHashList([]string{batchHash})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupCommitted, statuses[0])
|
||||
|
||||
// process committed batch and check status
|
||||
l2Relayer.ProcessCommittedBatches()
|
||||
|
||||
blockBatchWithFinalizeTxHash, err := blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batchHash}, nil, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(blockBatchWithFinalizeTxHash))
|
||||
assert.NotEmpty(t, blockBatchWithFinalizeTxHash[0].FinalizeTxHash)
|
||||
assert.Equal(t, types.RollupFinalizing, types.RollupStatus(blockBatchWithFinalizeTxHash[0].RollupStatus))
|
||||
|
||||
finalizeTx, _, err := l1Client.TransactionByHash(context.Background(), common.HexToHash(blockBatchWithFinalizeTxHash[0].FinalizeTxHash))
|
||||
assert.NoError(t, err)
|
||||
finalizeTxReceipt, err := bind.WaitMined(context.Background(), l1Client, finalizeTx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(finalizeTxReceipt.Logs), 1)
|
||||
|
||||
// fetch FinalizeBatch events
|
||||
err = l1Watcher.FetchContractEvent()
|
||||
assert.NoError(t, err)
|
||||
statuses, err = blockBatchOrm.GetRollupStatusByHashList([]string{batchHash})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupFinalized, statuses[0])
|
||||
|
||||
// process l2 messages
|
||||
l2Relayer.ProcessSavedEvents()
|
||||
|
||||
l2Messages, err := l2MessageOrm.GetL2Messages(map[string]interface{}{"nonce": nonce.Uint64()}, nil, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(l2Messages))
|
||||
assert.NotEmpty(t, l2Messages[0].Layer1Hash)
|
||||
assert.Equal(t, types.MsgStatus(l2Messages[0].Status), types.MsgSubmitted)
|
||||
|
||||
relayTx, _, err := l1Client.TransactionByHash(context.Background(), common.HexToHash(l2Messages[0].Layer1Hash))
|
||||
assert.NoError(t, err)
|
||||
relayTxReceipt, err := bind.WaitMined(context.Background(), l1Client, relayTx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(relayTxReceipt.Logs), 1)
|
||||
|
||||
// fetch message relayed events
|
||||
err = l1Watcher.FetchContractEvent()
|
||||
assert.NoError(t, err)
|
||||
msg, err = l2MessageOrm.GetL2MessageByNonce(nonce.Uint64())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.MsgStatus(msg.Status), types.MsgConfirmed)
|
||||
}
|
||||
58
bridge/tests/process_start_test.go
Normal file
58
bridge/tests/process_start_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
_ "scroll-tech/bridge/cmd/event_watcher/app"
|
||||
_ "scroll-tech/bridge/cmd/gas_oracle/app"
|
||||
_ "scroll-tech/bridge/cmd/msg_relayer/app"
|
||||
_ "scroll-tech/bridge/cmd/rollup_relayer/app"
|
||||
|
||||
cutils "scroll-tech/common/utils"
|
||||
|
||||
"scroll-tech/bridge/internal/utils"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testProcessStart(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
defer utils.CloseDB(db)
|
||||
|
||||
bridgeApp.RunApp(t, cutils.EventWatcherApp)
|
||||
bridgeApp.RunApp(t, cutils.GasOracleApp)
|
||||
bridgeApp.RunApp(t, cutils.MessageRelayerApp)
|
||||
bridgeApp.RunApp(t, cutils.RollupRelayerApp)
|
||||
|
||||
bridgeApp.WaitExit()
|
||||
}
|
||||
|
||||
func testProcessStartEnableMetrics(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
defer utils.CloseDB(db)
|
||||
|
||||
port, err := rand.Int(rand.Reader, big.NewInt(2000))
|
||||
assert.NoError(t, err)
|
||||
svrPort := strconv.FormatInt(port.Int64()+50000, 10)
|
||||
bridgeApp.RunApp(t, cutils.EventWatcherApp, "--metrics", "--metrics.addr", "localhost", "--metrics.port", svrPort)
|
||||
|
||||
port, err = rand.Int(rand.Reader, big.NewInt(2000))
|
||||
assert.NoError(t, err)
|
||||
svrPort = strconv.FormatInt(port.Int64()+50000, 10)
|
||||
bridgeApp.RunApp(t, cutils.GasOracleApp, "--metrics", "--metrics.addr", "localhost", "--metrics.port", svrPort)
|
||||
|
||||
port, err = rand.Int(rand.Reader, big.NewInt(2000))
|
||||
assert.NoError(t, err)
|
||||
svrPort = strconv.FormatInt(port.Int64()+50000, 10)
|
||||
bridgeApp.RunApp(t, cutils.MessageRelayerApp, "--metrics", "--metrics.addr", "localhost", "--metrics.port", svrPort)
|
||||
|
||||
port, err = rand.Int(rand.Reader, big.NewInt(2000))
|
||||
assert.NoError(t, err)
|
||||
svrPort = strconv.FormatInt(port.Int64()+50000, 10)
|
||||
bridgeApp.RunApp(t, cutils.RollupRelayerApp, "--metrics", "--metrics.addr", "localhost", "--metrics.port", svrPort)
|
||||
|
||||
bridgeApp.WaitExit()
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
@@ -10,10 +9,11 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"scroll-tech/common/types"
|
||||
"scroll-tech/common/types/message"
|
||||
|
||||
"scroll-tech/bridge/internal/config"
|
||||
"scroll-tech/bridge/internal/controller/relayer"
|
||||
"scroll-tech/bridge/internal/controller/watcher"
|
||||
"scroll-tech/bridge/internal/orm"
|
||||
@@ -29,22 +29,19 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
|
||||
|
||||
// Create L2Relayer
|
||||
l2Cfg := bridgeApp.Config.L2Config
|
||||
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
|
||||
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create L1Watcher
|
||||
l1Cfg := bridgeApp.Config.L1Config
|
||||
l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, 0, l1Cfg.Confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
|
||||
|
||||
blockTraceOrm := orm.NewBlockTrace(db)
|
||||
|
||||
// add some blocks to db
|
||||
var wrappedBlocks []*bridgeTypes.WrappedBlock
|
||||
var parentHash common.Hash
|
||||
for i := 1; i <= 10; i++ {
|
||||
for i := 0; i < 10; i++ {
|
||||
header := gethTypes.Header{
|
||||
Number: big.NewInt(int64(i)),
|
||||
ParentHash: parentHash,
|
||||
ParentHash: common.Hash{},
|
||||
Difficulty: big.NewInt(0),
|
||||
BaseFee: big.NewInt(0),
|
||||
}
|
||||
@@ -53,56 +50,47 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
|
||||
Transactions: nil,
|
||||
WithdrawTrieRoot: common.Hash{},
|
||||
})
|
||||
parentHash = header.Hash()
|
||||
}
|
||||
assert.NoError(t, blockTraceOrm.InsertWrappedBlocks(wrappedBlocks))
|
||||
|
||||
parentBatch := &bridgeTypes.BatchInfo{
|
||||
Index: 0,
|
||||
Hash: "0x0000000000000000000000000000000000000000",
|
||||
}
|
||||
|
||||
tmpWrapBlocks := []*bridgeTypes.WrappedBlock{
|
||||
wrappedBlocks[0],
|
||||
wrappedBlocks[1],
|
||||
}
|
||||
batchData := bridgeTypes.NewBatchData(parentBatch, tmpWrapBlocks, l2Cfg.BatchProposerConfig.PublicInputConfig)
|
||||
|
||||
batchHash := batchData.Hash().String()
|
||||
|
||||
blockBatchOrm := orm.NewBlockBatch(db)
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
rowsAffected, dbTxErr := blockBatchOrm.InsertBlockBatchByBatchData(tx, batchData)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
if rowsAffected != 1 {
|
||||
dbTxErr = errors.New("the InsertBlockBatchByBatchData affected row is not 1")
|
||||
return dbTxErr
|
||||
}
|
||||
var blockIDs = make([]uint64, len(batchData.Batch.Blocks))
|
||||
for i, block := range batchData.Batch.Blocks {
|
||||
blockIDs[i] = block.BlockNumber
|
||||
}
|
||||
dbTxErr = blockTraceOrm.UpdateBatchHashForL2Blocks(tx, blockIDs, batchHash)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
l2BlockOrm := orm.NewL2Block(db)
|
||||
err = l2BlockOrm.InsertL2Blocks(context.Background(), wrappedBlocks)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// process pending batch and check status
|
||||
assert.NoError(t, l2Relayer.SendCommitTx([]*bridgeTypes.BatchData{batchData}))
|
||||
cp := watcher.NewChunkProposer(context.Background(), &config.ChunkProposerConfig{
|
||||
MaxTxGasPerChunk: 1000000000,
|
||||
MaxL2TxNumPerChunk: 10000,
|
||||
MaxL1CommitGasPerChunk: 50000000000,
|
||||
MaxL1CommitCalldataSizePerChunk: 1000000,
|
||||
MinL1CommitCalldataSizePerChunk: 0,
|
||||
ChunkTimeoutSec: 300,
|
||||
}, db)
|
||||
cp.TryProposeChunk()
|
||||
|
||||
blockBatches, err := blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batchHash}, nil, 1)
|
||||
chunkOrm := orm.NewChunk(db)
|
||||
chunks, err := chunkOrm.GetUnbatchedChunks(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(blockBatches))
|
||||
assert.NotEmpty(t, true, blockBatches[0].CommitTxHash)
|
||||
assert.NotEmpty(t, true, blockBatches[0].RollupStatus)
|
||||
assert.Equal(t, types.RollupStatus(blockBatches[0].RollupStatus), types.RollupCommitting)
|
||||
assert.Len(t, chunks, 1)
|
||||
|
||||
commitTx, _, err := l1Client.TransactionByHash(context.Background(), common.HexToHash(blockBatches[0].CommitTxHash))
|
||||
bp := watcher.NewBatchProposer(context.Background(), &config.BatchProposerConfig{
|
||||
MaxChunkNumPerBatch: 10,
|
||||
MaxL1CommitGasPerBatch: 50000000000,
|
||||
MaxL1CommitCalldataSizePerBatch: 1000000,
|
||||
MinChunkNumPerBatch: 1,
|
||||
BatchTimeoutSec: 300,
|
||||
}, db)
|
||||
bp.TryProposeBatch()
|
||||
|
||||
l2Relayer.ProcessPendingBatches()
|
||||
|
||||
batchOrm := orm.NewBatch(db)
|
||||
batch, err := batchOrm.GetLatestBatch(context.Background())
|
||||
assert.NoError(t, err)
|
||||
batchHash := batch.Hash
|
||||
assert.NotEmpty(t, batch.CommitTxHash)
|
||||
assert.Equal(t, types.RollupCommitting, types.RollupStatus(batch.RollupStatus))
|
||||
|
||||
assert.NoError(t, err)
|
||||
commitTx, _, err := l1Client.TransactionByHash(context.Background(), common.HexToHash(batch.CommitTxHash))
|
||||
assert.NoError(t, err)
|
||||
commitTxReceipt, err := bind.WaitMined(context.Background(), l1Client, commitTx)
|
||||
assert.NoError(t, err)
|
||||
@@ -111,33 +99,34 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
|
||||
// fetch rollup events
|
||||
err = l1Watcher.FetchContractEvent()
|
||||
assert.NoError(t, err)
|
||||
statuses, err := blockBatchOrm.GetRollupStatusByHashList([]string{batchHash})
|
||||
statuses, err := batchOrm.GetRollupStatusByHashList(context.Background(), []string{batchHash})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupCommitted, statuses[0])
|
||||
|
||||
// add dummy proof
|
||||
tProof := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
tInstanceCommitments := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
err = blockBatchOrm.UpdateProofByHash(context.Background(), batchHash, tProof, tInstanceCommitments, 100)
|
||||
proof := &message.AggProof{
|
||||
Proof: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
|
||||
FinalPair: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
|
||||
}
|
||||
err = batchOrm.UpdateProofByHash(context.Background(), batchHash, proof, 100)
|
||||
assert.NoError(t, err)
|
||||
err = blockBatchOrm.UpdateProvingStatus(batchHash, types.ProvingTaskVerified)
|
||||
err = batchOrm.UpdateProvingStatus(context.Background(), batchHash, types.ProvingTaskVerified)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// process committed batch and check status
|
||||
l2Relayer.ProcessCommittedBatches()
|
||||
|
||||
statuses, err = blockBatchOrm.GetRollupStatusByHashList([]string{batchHash})
|
||||
statuses, err = batchOrm.GetRollupStatusByHashList(context.Background(), []string{batchHash})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupFinalizing, statuses[0])
|
||||
|
||||
blockBatches, err = blockBatchOrm.GetBlockBatches(map[string]interface{}{"hash": batchHash}, nil, 1)
|
||||
batch, err = batchOrm.GetLatestBatch(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(blockBatches))
|
||||
assert.NotEmpty(t, blockBatches[0].FinalizeTxHash)
|
||||
assert.NotEmpty(t, batch.FinalizeTxHash)
|
||||
|
||||
finalizeTx, _, err := l1Client.TransactionByHash(context.Background(), common.HexToHash(blockBatches[0].FinalizeTxHash))
|
||||
finalizeTx, _, err := l1Client.TransactionByHash(context.Background(), common.HexToHash(batch.FinalizeTxHash))
|
||||
assert.NoError(t, err)
|
||||
finalizeTxReceipt, err := bind.WaitMined(context.Background(), l1Client, finalizeTx)
|
||||
assert.NoError(t, err)
|
||||
@@ -146,7 +135,7 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
|
||||
// fetch rollup events
|
||||
err = l1Watcher.FetchContractEvent()
|
||||
assert.NoError(t, err)
|
||||
statuses, err = blockBatchOrm.GetRollupStatusByHashList([]string{batchHash})
|
||||
statuses, err = batchOrm.GetRollupStatusByHashList(context.Background(), []string{batchHash})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(statuses))
|
||||
assert.Equal(t, types.RollupFinalized, statuses[0])
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user