Commit 25f10683 authored by Adrian Sutton's avatar Adrian Sutton

Merge remote-tracking branch 'origin/develop' into aj/geth-1.13.4

parents 40d15216 a029c870
...@@ -1254,6 +1254,22 @@ jobs: ...@@ -1254,6 +1254,22 @@ jobs:
event: fail event: fail
template: basic_fail_1 template: basic_fail_1
op-program-compat:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
steps:
- checkout
- restore_cache:
name: Restore Go modules cache
key: gomod-{{ checksum "go.sum" }}
- restore_cache:
key: golang-build-cache
- run:
name: compat-goerli
command: |
make run-goerli-verify
working_directory: op-program
check-generated-mocks-op-node: check-generated-mocks-op-node:
docker: docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
...@@ -1432,6 +1448,9 @@ workflows: ...@@ -1432,6 +1448,9 @@ workflows:
- op-stack-go-lint - op-stack-go-lint
- devnet-allocs - devnet-allocs
- l1-geth-version-check - l1-geth-version-check
- op-program-compat:
requires:
- op-program-tests
- bedrock-go-tests: - bedrock-go-tests:
requires: requires:
- go-mod-download - go-mod-download
...@@ -1449,6 +1468,7 @@ workflows: ...@@ -1449,6 +1468,7 @@ workflows:
- op-proposer-tests - op-proposer-tests
- op-challenger-tests - op-challenger-tests
- op-program-tests - op-program-tests
- op-program-compat
- op-service-tests - op-service-tests
- op-e2e-WS-tests - op-e2e-WS-tests
- op-e2e-HTTP-tests - op-e2e-HTTP-tests
......
...@@ -181,9 +181,9 @@ You must have Python 3.x installed to run `slither`. ...@@ -181,9 +181,9 @@ You must have Python 3.x installed to run `slither`.
To run `slither` locally, do: To run `slither` locally, do:
```bash ```bash
cd packages/contracts cd packages/contracts-bedrock
pip3 install slither-analyzer pip3 install slither-analyzer
pnpm test:slither pnpm slither
``` ```
## Labels ## Labels
......
...@@ -94,8 +94,13 @@ def main(): ...@@ -94,8 +94,13 @@ def main():
devnet_l1_genesis(paths) devnet_l1_genesis(paths)
return return
log.info('Building docker images') git_commit = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True).stdout.strip()
run_command(['docker', 'compose', 'build', '--progress', 'plain'], cwd=paths.ops_bedrock_dir, env={ git_date = subprocess.run(['git', 'show', '-s', "--format=%ct"], capture_output=True, text=True).stdout.strip()
log.info(f'Building docker images for git commit {git_commit} ({git_date})')
run_command(['docker', 'compose', 'build', '--progress', 'plain',
'--build-arg', f'GIT_COMMIT={git_commit}', '--build-arg', f'GIT_DATE={git_date}'],
cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir, 'PWD': paths.ops_bedrock_dir,
'DOCKER_BUILDKIT': '1', # (should be available by default in later versions, but explicitly enable it anyway) 'DOCKER_BUILDKIT': '1', # (should be available by default in later versions, but explicitly enable it anyway)
'COMPOSE_DOCKER_CLI_BUILD': '1' # use the docker cache 'COMPOSE_DOCKER_CLI_BUILD': '1' # use the docker cache
......
package main package main
import ( import (
"fmt"
"os" "os"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -22,7 +22,7 @@ func main() { ...@@ -22,7 +22,7 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Flags = endpointMonitor.CLIFlags("ENDPOINT_MONITOR") app.Flags = endpointMonitor.CLIFlags("ENDPOINT_MONITOR")
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate) app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "endpoint-monitor" app.Name = "endpoint-monitor"
app.Usage = "Endpoint Monitoring Service" app.Usage = "Endpoint Monitoring Service"
app.Description = "" app.Description = ""
......
...@@ -9,8 +9,8 @@ require ( ...@@ -9,8 +9,8 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231018202221-fdba3d104171 github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231018202221-fdba3d104171
github.com/ethereum/go-ethereum v1.13.4 github.com/ethereum/go-ethereum v1.13.1
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.7.0
github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/docgen v1.2.0 github.com/go-chi/docgen v1.2.0
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
...@@ -33,7 +33,7 @@ require ( ...@@ -33,7 +33,7 @@ require (
github.com/multiformats/go-multiaddr v0.12.0 github.com/multiformats/go-multiaddr v0.12.0
github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.28.0 github.com/onsi/gomega v1.28.1
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0 github.com/pkg/profile v1.7.0
github.com/prometheus/client_golang v1.17.0 github.com/prometheus/client_golang v1.17.0
...@@ -62,7 +62,6 @@ require ( ...@@ -62,7 +62,6 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
// Upgraded to include https://github.com/cockroachdb/pebble/pull/3005
github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4 // indirect github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
...@@ -163,7 +162,7 @@ require ( ...@@ -163,7 +162,7 @@ require (
github.com/multiformats/go-varint v0.0.7 // indirect github.com/multiformats/go-varint v0.0.7 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 // indirect
github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/onsi/ginkgo/v2 v2.13.0 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
...@@ -210,7 +209,7 @@ require ( ...@@ -210,7 +209,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect rsc.io/tmplfunc v0.0.3 // indirect
) )
replace github.com/ethereum/go-ethereum v1.13.4 => github.com/ethereum-optimism/op-geth v1.101303.0-rc.2.0.20231023000909-ceb5f3201eb7 replace github.com/ethereum/go-ethereum v1.13.1 => github.com/ethereum-optimism/op-geth v1.101303.0-rc.2.0.20231024175019-29cd9a353f83
//replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain //replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain
//replace github.com/ethereum/go-ethereum v1.13.4 => ../go-ethereum //replace github.com/ethereum/go-ethereum v1.13.1 => ../go-ethereum
...@@ -151,8 +151,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/ ...@@ -151,8 +151,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101303.0-rc.2.0.20231023000909-ceb5f3201eb7 h1:4T6bQlhdoPpQDSGUO4wstmyipeX8ATMKGVISSwe4fbY= github.com/ethereum-optimism/op-geth v1.101303.0-rc.2.0.20231024175019-29cd9a353f83 h1:RFKnTUJqbYM8+dueFcGPdOY0ycrOhxp0HQJyy2OYzvc=
github.com/ethereum-optimism/op-geth v1.101303.0-rc.2.0.20231023000909-ceb5f3201eb7/go.mod h1:hl28ffXoV4maInP7dvhvNgDO79Q5M3MEYrPZZO6u3W8= github.com/ethereum-optimism/op-geth v1.101303.0-rc.2.0.20231024175019-29cd9a353f83/go.mod h1:hl28ffXoV4maInP7dvhvNgDO79Q5M3MEYrPZZO6u3W8=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231018202221-fdba3d104171 h1:MjCUj16JSLZRDnQQ6OOUy6Chfb4dKo7ahFceNi0RKZ8= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231018202221-fdba3d104171 h1:MjCUj16JSLZRDnQQ6OOUy6Chfb4dKo7ahFceNi0RKZ8=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231018202221-fdba3d104171/go.mod h1:/70H/KqrtKcvWvNGVj6S3rAcLC+kUPr3t2aDmYIS+Xk= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231018202221-fdba3d104171/go.mod h1:/70H/KqrtKcvWvNGVj6S3rAcLC+kUPr3t2aDmYIS+Xk=
github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg=
...@@ -171,8 +171,8 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD ...@@ -171,8 +171,8 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b h1:vMT47RYsrftsHSTQhqXwC3BYflo38OLC3Y4LtXtLyU0= github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b h1:vMT47RYsrftsHSTQhqXwC3BYflo38OLC3Y4LtXtLyU0=
...@@ -561,16 +561,16 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv ...@@ -561,16 +561,16 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
...@@ -873,7 +873,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc ...@@ -873,7 +873,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
......
...@@ -186,7 +186,6 @@ func (db *blocksDB) LatestObservedEpoch(fromL1Height *big.Int, maxL1Range uint64 ...@@ -186,7 +186,6 @@ func (db *blocksDB) LatestObservedEpoch(fromL1Height *big.Int, maxL1Range uint64
var header L1BlockHeader var header L1BlockHeader
if fromL1Height != nil { if fromL1Height != nil {
result := db.gorm.Where("number = ?", fromL1Height).Take(&header) result := db.gorm.Where("number = ?", fromL1Height).Take(&header)
// TODO - Embed logging to db
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
...@@ -196,7 +195,8 @@ func (db *blocksDB) LatestObservedEpoch(fromL1Height *big.Int, maxL1Range uint64 ...@@ -196,7 +195,8 @@ func (db *blocksDB) LatestObservedEpoch(fromL1Height *big.Int, maxL1Range uint64
fromTimestamp = header.Timestamp fromTimestamp = header.Timestamp
} else { } else {
result := db.gorm.Order("number desc").Take(&header) // Take the lowest indexed L1 block to compute the lower bound
result := db.gorm.Order("number ASC").Take(&header)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
...@@ -205,6 +205,7 @@ func (db *blocksDB) LatestObservedEpoch(fromL1Height *big.Int, maxL1Range uint64 ...@@ -205,6 +205,7 @@ func (db *blocksDB) LatestObservedEpoch(fromL1Height *big.Int, maxL1Range uint64
} }
fromL1Height = header.Number fromL1Height = header.Number
fromTimestamp = header.Timestamp
} }
// Upper Bound (lowest timestamp indexed between L1/L2 bounded by `maxL1Range`) // Upper Bound (lowest timestamp indexed between L1/L2 bounded by `maxL1Range`)
......
...@@ -58,7 +58,6 @@ services: ...@@ -58,7 +58,6 @@ services:
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
depends_on:
migrations: migrations:
condition: service_started condition: service_started
......
...@@ -177,26 +177,6 @@ CREATE INDEX IF NOT EXISTS l2_bridge_messages_transaction_withdrawal_hash ON l2_ ...@@ -177,26 +177,6 @@ CREATE INDEX IF NOT EXISTS l2_bridge_messages_transaction_withdrawal_hash ON l2_
CREATE INDEX IF NOT EXISTS l2_bridge_messages_from_address ON l2_bridge_messages(from_address); CREATE INDEX IF NOT EXISTS l2_bridge_messages_from_address ON l2_bridge_messages(from_address);
-- StandardBridge -- StandardBridge
CREATE TABLE IF NOT EXISTS l1_bridged_tokens (
address VARCHAR PRIMARY KEY,
bridge_address VARCHAR NOT NULL,
name VARCHAR NOT NULL,
symbol VARCHAR NOT NULL,
decimals INTEGER NOT NULL CHECK (decimals >= 0 AND decimals <= 18)
);
CREATE TABLE IF NOT EXISTS l2_bridged_tokens (
address VARCHAR PRIMARY KEY,
bridge_address VARCHAR NOT NULL,
-- L1-L2 relationship is 1 to many so this is not necessarily unique
l1_token_address VARCHAR REFERENCES l1_bridged_tokens(address) ON DELETE CASCADE,
name VARCHAR NOT NULL,
symbol VARCHAR NOT NULL,
decimals INTEGER NOT NULL CHECK (decimals >= 0 AND decimals <= 18)
);
CREATE TABLE IF NOT EXISTS l1_bridge_deposits ( CREATE TABLE IF NOT EXISTS l1_bridge_deposits (
transaction_source_hash VARCHAR PRIMARY KEY REFERENCES l1_transaction_deposits(source_hash) ON DELETE CASCADE, transaction_source_hash VARCHAR PRIMARY KEY REFERENCES l1_transaction_deposits(source_hash) ON DELETE CASCADE,
cross_domain_message_hash VARCHAR NOT NULL UNIQUE REFERENCES l1_bridge_messages(message_hash) ON DELETE CASCADE, cross_domain_message_hash VARCHAR NOT NULL UNIQUE REFERENCES l1_bridge_messages(message_hash) ON DELETE CASCADE,
...@@ -204,8 +184,8 @@ CREATE TABLE IF NOT EXISTS l1_bridge_deposits ( ...@@ -204,8 +184,8 @@ CREATE TABLE IF NOT EXISTS l1_bridge_deposits (
-- Deposit information -- Deposit information
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
local_token_address VARCHAR NOT NULL, -- REFERENCES l1_bridged_tokens(address), uncomment me in future pr local_token_address VARCHAR NOT NULL,
remote_token_address VARCHAR NOT NULL, -- REFERENCES l2_bridged_tokens(address), uncomment me in future pr remote_token_address VARCHAR NOT NULL,
amount UINT256 NOT NULL, amount UINT256 NOT NULL,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
...@@ -221,8 +201,8 @@ CREATE TABLE IF NOT EXISTS l2_bridge_withdrawals ( ...@@ -221,8 +201,8 @@ CREATE TABLE IF NOT EXISTS l2_bridge_withdrawals (
-- Withdrawal information -- Withdrawal information
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
local_token_address VARCHAR NOT NULL, -- REFERENCES l2_bridged_tokens(address), uncomment me in future pr local_token_address VARCHAR NOT NULL,
remote_token_address VARCHAR NOT NULL, -- REFERENCES l1_bridged_tokens(address), uncomment me in future pr remote_token_address VARCHAR NOT NULL,
amount UINT256 NOT NULL, amount UINT256 NOT NULL,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
......
...@@ -29,7 +29,7 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -29,7 +29,7 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected transaction deposits", "size", len(optimismPortalTxDeposits)) log.Info("detected transaction deposits", "size", len(optimismPortalTxDeposits))
} }
var mintedGWEI = bigint.Zero mintedGWEI := bigint.Zero
portalDeposits := make(map[logKey]*contracts.OptimismPortalTransactionDepositEvent, len(optimismPortalTxDeposits)) portalDeposits := make(map[logKey]*contracts.OptimismPortalTransactionDepositEvent, len(optimismPortalTxDeposits))
transactionDeposits := make([]database.L1TransactionDeposit, len(optimismPortalTxDeposits)) transactionDeposits := make([]database.L1TransactionDeposit, len(optimismPortalTxDeposits))
for i := range optimismPortalTxDeposits { for i := range optimismPortalTxDeposits {
...@@ -44,7 +44,6 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -44,7 +44,6 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
GasLimit: depositTx.GasLimit, GasLimit: depositTx.GasLimit,
Tx: depositTx.Tx, Tx: depositTx.Tx,
} }
} }
if len(transactionDeposits) > 0 { if len(transactionDeposits) > 0 {
...@@ -125,6 +124,7 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -125,6 +124,7 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
} }
bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++ bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
bridgeDeposits[i] = database.L1BridgeDeposit{ bridgeDeposits[i] = database.L1BridgeDeposit{
TransactionSourceHash: portalDeposit.DepositTx.SourceHash, TransactionSourceHash: portalDeposit.DepositTx.SourceHash,
...@@ -214,10 +214,8 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -214,10 +214,8 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages)) log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
} }
relayedMessages := make(map[logKey]*contracts.CrossDomainMessengerRelayedMessageEvent, len(crossDomainRelayedMessages))
for i := range crossDomainRelayedMessages { for i := range crossDomainRelayedMessages {
relayed := crossDomainRelayedMessages[i] relayed := crossDomainRelayedMessages[i]
relayedMessages[logKey{BlockHash: relayed.Event.BlockHash, LogIndex: relayed.Event.LogIndex}] = &relayed
message, err := db.BridgeMessages.L2BridgeMessage(relayed.MessageHash) message, err := db.BridgeMessages.L2BridgeMessage(relayed.MessageHash)
if err != nil { if err != nil {
return err return err
...@@ -236,40 +234,21 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -236,40 +234,21 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
} }
// (4) L1StandardBridge // (4) L1StandardBridge
// - Nothing actionable on the database. Since the StandardBridge is layered ontop of the
// CrossDomainMessenger, there's no need for any sanity or invariant checks as the previous step
// ensures a relayed message (finalized bridge) can be linked with a sent message (initiated bridge).
finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight) finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
if err != nil { if err != nil {
return err return err
} }
if len(finalizedBridges) > 0 {
log.Info("detected finalized bridge withdrawals", "size", len(finalizedBridges))
}
finalizedTokens := make(map[common.Address]int) finalizedTokens := make(map[common.Address]int)
for i := range finalizedBridges { for i := range finalizedBridges {
// Nothing actionable on the database. However, we can treat the relayed message
// as an invariant by ensuring we can query for a deposit by the same hash
finalizedBridge := finalizedBridges[i] finalizedBridge := finalizedBridges[i]
relayedMessage, ok := relayedMessages[logKey{finalizedBridge.Event.BlockHash, finalizedBridge.Event.LogIndex + 1}]
if !ok {
log.Error("expected RelayedMessage following BridgeFinalized event", "tx_hash", finalizedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected RelayedMessage following BridgeFinalized event. tx_hash = %s", finalizedBridge.Event.TransactionHash.String())
} else if relayedMessage.Event.TransactionHash != finalizedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "message_tx_hash", relayedMessage.Event.TransactionHash.String(), "bridge_tx_hash", finalizedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
}
// Since the message hash is computed from the relayed message, this ensures the deposit fields must match
withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalWithFilter(database.BridgeTransfer{CrossDomainMessageHash: &relayedMessage.MessageHash})
if err != nil {
return err
} else if withdrawal == nil {
log.Error("missing L2StandardBridge withdrawal on L1 finalization", "tx_hash", finalizedBridge.Event.TransactionHash.String())
return fmt.Errorf("missing L2StandardBridge withdrawal on L1 finalization. tx_hash: %s", finalizedBridge.Event.TransactionHash.String())
}
finalizedTokens[finalizedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++ finalizedTokens[finalizedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
} }
if len(finalizedBridges) > 0 { if len(finalizedBridges) > 0 {
log.Info("detected finalized bridge withdrawals", "size", len(finalizedBridges))
for tokenAddr, size := range finalizedTokens { for tokenAddr, size := range finalizedTokens {
metrics.RecordL1FinalizedBridgeTransfers(tokenAddr, size) metrics.RecordL1FinalizedBridgeTransfers(tokenAddr, size)
} }
......
package bridge package bridge
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
...@@ -29,7 +28,7 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L2M ...@@ -29,7 +28,7 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L2M
log.Info("detected transaction withdrawals", "size", len(l2ToL1MPMessagesPassed)) log.Info("detected transaction withdrawals", "size", len(l2ToL1MPMessagesPassed))
} }
var withdrawnWEI = bigint.Zero withdrawnWEI := bigint.Zero
messagesPassed := make(map[logKey]*contracts.L2ToL1MessagePasserMessagePassed, len(l2ToL1MPMessagesPassed)) messagesPassed := make(map[logKey]*contracts.L2ToL1MessagePasserMessagePassed, len(l2ToL1MPMessagesPassed))
transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(l2ToL1MPMessagesPassed)) transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(l2ToL1MPMessagesPassed))
for i := range l2ToL1MPMessagesPassed { for i := range l2ToL1MPMessagesPassed {
...@@ -122,8 +121,9 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L2M ...@@ -122,8 +121,9 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L2M
return fmt.Errorf("correlated events tx hash mismatch") return fmt.Errorf("correlated events tx hash mismatch")
} }
initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++ bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
bridgeWithdrawals[i] = database.L2BridgeWithdrawal{ bridgeWithdrawals[i] = database.L2BridgeWithdrawal{
TransactionWithdrawalHash: messagePassed.WithdrawalHash, TransactionWithdrawalHash: messagePassed.WithdrawalHash,
BridgeTransfer: initiatedBridge.BridgeTransfer, BridgeTransfer: initiatedBridge.BridgeTransfer,
...@@ -158,10 +158,8 @@ func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L2M ...@@ -158,10 +158,8 @@ func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L2M
log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages)) log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
} }
relayedMessages := make(map[logKey]*contracts.CrossDomainMessengerRelayedMessageEvent, len(crossDomainRelayedMessages))
for i := range crossDomainRelayedMessages { for i := range crossDomainRelayedMessages {
relayed := crossDomainRelayedMessages[i] relayed := crossDomainRelayedMessages[i]
relayedMessages[logKey{BlockHash: relayed.Event.BlockHash, LogIndex: relayed.Event.LogIndex}] = &relayed
message, err := db.BridgeMessages.L1BridgeMessage(relayed.MessageHash) message, err := db.BridgeMessages.L1BridgeMessage(relayed.MessageHash)
if err != nil { if err != nil {
return err return err
...@@ -175,45 +173,26 @@ func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L2M ...@@ -175,45 +173,26 @@ func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L2M
return err return err
} }
} }
if len(relayedMessages) > 0 { if len(crossDomainRelayedMessages) > 0 {
metrics.RecordL2CrossDomainRelayedMessages(len(relayedMessages)) metrics.RecordL2CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
} }
// (2) L2StandardBridge // (2) L2StandardBridge
// - Nothing actionable on the database. Since the StandardBridge is layered ontop of the
// CrossDomainMessenger, there's no need for any sanity or invariant checks as the previous step
// ensures a relayed message (finalized bridge) can be linked with a sent message (initiated bridge).
finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l2", l2Contracts.L2StandardBridge, db, fromHeight, toHeight) finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l2", l2Contracts.L2StandardBridge, db, fromHeight, toHeight)
if err != nil { if err != nil {
return err return err
} }
if len(finalizedBridges) > 0 {
log.Info("detected finalized bridge deposits", "size", len(finalizedBridges))
}
finalizedTokens := make(map[common.Address]int) finalizedTokens := make(map[common.Address]int)
for i := range finalizedBridges { for i := range finalizedBridges {
// Nothing actionable on the database. However, we can treat the relayed message
// as an invariant by ensuring we can query for a deposit by the same hash
finalizedBridge := finalizedBridges[i] finalizedBridge := finalizedBridges[i]
relayedMessage, ok := relayedMessages[logKey{finalizedBridge.Event.BlockHash, finalizedBridge.Event.LogIndex + 1}]
if !ok {
log.Error("expected RelayedMessage following BridgeFinalized event", "tx_hash", finalizedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected RelayedMessage following BridgeFinalized event. tx_hash = %s", finalizedBridge.Event.TransactionHash.String())
} else if relayedMessage.Event.TransactionHash != finalizedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "message_tx_hash", relayedMessage.Event.TransactionHash.String(), "bridge_tx_hash", finalizedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
}
// Since the message hash is computed from the relayed message, this ensures the withdrawal fields must match
deposit, err := db.BridgeTransfers.L1BridgeDepositWithFilter(database.BridgeTransfer{CrossDomainMessageHash: &relayedMessage.MessageHash})
if err != nil {
return err
} else if deposit == nil {
log.Error("missing L1StandardBridge deposit on L2 finalization", "tx_hash", finalizedBridge.Event.TransactionHash.String())
return errors.New("missing L1StandardBridge deposit on L2 finalization")
}
finalizedTokens[finalizedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++ finalizedTokens[finalizedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
} }
if len(finalizedBridges) > 0 { if len(finalizedBridges) > 0 {
log.Info("detected finalized bridge deposits", "size", len(finalizedBridges))
for tokenAddr, size := range finalizedTokens { for tokenAddr, size := range finalizedTokens {
metrics.RecordL2FinalizedBridgeTransfers(tokenAddr, size) metrics.RecordL2FinalizedBridgeTransfers(tokenAddr, size)
} }
......
...@@ -162,13 +162,17 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -162,13 +162,17 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
log.Info("detected legacy transaction withdrawals (via L2CrossDomainMessenger)", "size", len(crossDomainSentMessages)) log.Info("detected legacy transaction withdrawals (via L2CrossDomainMessenger)", "size", len(crossDomainSentMessages))
} }
type sentMessageEvent struct {
*contracts.CrossDomainMessengerSentMessageEvent
WithdrawalHash common.Hash
}
withdrawnWEI := bigint.Zero withdrawnWEI := bigint.Zero
sentMessages := make(map[logKey]*contracts.CrossDomainMessengerSentMessageEvent, len(crossDomainSentMessages)) sentMessages := make(map[logKey]sentMessageEvent, len(crossDomainSentMessages))
bridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages)) bridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages))
transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(crossDomainSentMessages)) transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(crossDomainSentMessages))
for i := range crossDomainSentMessages { for i := range crossDomainSentMessages {
sentMessage := crossDomainSentMessages[i] sentMessage := crossDomainSentMessages[i]
sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = &sentMessage
withdrawnWEI = new(big.Int).Add(withdrawnWEI, sentMessage.BridgeMessage.Tx.Amount) withdrawnWEI = new(big.Int).Add(withdrawnWEI, sentMessage.BridgeMessage.Tx.Amount)
// To ensure consistency in the schema, we duplicate this as the "root" transaction withdrawal. The storage key in the message // To ensure consistency in the schema, we duplicate this as the "root" transaction withdrawal. The storage key in the message
...@@ -188,6 +192,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -188,6 +192,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
}, },
} }
sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = sentMessageEvent{&sentMessage, withdrawalHash}
bridgeMessages[i] = database.L2BridgeMessage{ bridgeMessages[i] = database.L2BridgeMessage{
TransactionWithdrawalHash: withdrawalHash, TransactionWithdrawalHash: withdrawalHash,
BridgeMessage: sentMessage.BridgeMessage, BridgeMessage: sentMessage.BridgeMessage,
...@@ -235,7 +240,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -235,7 +240,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++ bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
l2BridgeWithdrawals[i] = database.L2BridgeWithdrawal{ l2BridgeWithdrawals[i] = database.L2BridgeWithdrawal{
TransactionWithdrawalHash: sentMessage.BridgeMessage.MessageHash, TransactionWithdrawalHash: sentMessage.WithdrawalHash,
BridgeTransfer: initiatedBridge.BridgeTransfer, BridgeTransfer: initiatedBridge.BridgeTransfer,
} }
} }
...@@ -330,10 +335,12 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -330,10 +335,12 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
log.Warn("skipped pre-regensis relayed L2CrossDomainMessenger withdrawals", "size", skippedPreRegenesisMessages) log.Warn("skipped pre-regensis relayed L2CrossDomainMessenger withdrawals", "size", skippedPreRegenesisMessages)
} }
// (2) L2StandardBridge -- no-op for now as there's nothing actionable to do here besides // (2) L1StandardBridge
// santiy checks which is not important for legacy code. Not worth extra code pre-bedrock. // - Nothing actionable on the database. Since the StandardBridge is layered ontop of the
// The message status is already tracked via the relayed bridge messed through the cross domain messenger. // CrossDomainMessenger, there's no need for any sanity or invariant checks as the previous step
// - NOTE: This means we dont increment metrics for finalized bridge transfers // ensures a relayed message (finalized bridge) can be linked with a sent message (initiated bridge).
// - NOTE: Ignoring metrics for pre-bedrock transfers
// a-ok! // a-ok!
return nil return nil
...@@ -372,10 +379,12 @@ func LegacyL2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -372,10 +379,12 @@ func LegacyL2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
metrics.RecordL2CrossDomainRelayedMessages(len(crossDomainRelayedMessages)) metrics.RecordL2CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
} }
// (2) L2StandardBridge -- no-op for now as there's nothing actionable to do here besides // (2) L2StandardBridge
// santiy checks which is not important for legacy code. Not worth the extra code pre-bedorck. // - Nothing actionable on the database. Since the StandardBridge is layered ontop of the
// The message status is already tracked via the relayed bridge messed through the cross domain messenger. // CrossDomainMessenger, there's no need for any sanity or invariant checks as the previous step
// - NOTE: This means we dont increment metrics for finalized bridge transfers // ensures a relayed message (finalized bridge) can be linked with a sent message (initiated bridge).
// - NOTE: Ignoring metrics for pre-bedrock transfers
// a-ok! // a-ok!
return nil return nil
......
...@@ -3,110 +3,32 @@ package batcher ...@@ -3,110 +3,32 @@ package batcher
import ( import (
"context" "context"
"fmt" "fmt"
_ "net/http/pprof"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
) )
// Main is the entrypoint into the Batch Submitter. This method returns a // Main is the entrypoint into the Batch Submitter.
// closure that executes the service and blocks until the service exits. The use // This method returns a cliapp.LifecycleAction, to create an op-service CLI-lifecycle-managed batch-submitter with.
// of a closure allows the parameters bound to the top-level main package, e.g. func Main(version string) cliapp.LifecycleAction {
// GitVersion, to be captured and used once the function is executed. return func(cliCtx *cli.Context, closeApp context.CancelCauseFunc) (cliapp.Lifecycle, error) {
func Main(version string, cliCtx *cli.Context) error { if err := flags.CheckRequired(cliCtx); err != nil {
if err := flags.CheckRequired(cliCtx); err != nil { return nil, err
return err
}
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig)
oplog.SetGlobalLogHandler(l.GetHandler())
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
procName := "default"
m := metrics.NewMetrics(procName)
l.Info("Initializing Batch Submitter")
batchSubmitter, err := NewBatchSubmitterFromCLIConfig(cfg, l, m)
if err != nil {
l.Error("Unable to create Batch Submitter", "error", err)
return err
}
if !cfg.Stopped {
if err := batchSubmitter.Start(); err != nil {
l.Error("Unable to start Batch Submitter", "error", err)
return err
}
}
defer batchSubmitter.StopIfRunning(context.Background())
pprofConfig := cfg.PprofConfig
if pprofConfig.Enabled {
l.Debug("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
pprofSrv, err := oppprof.StartServer(pprofConfig.ListenAddr, pprofConfig.ListenPort)
if err != nil {
l.Error("failed to start pprof server", "err", err)
return err
} }
l.Info("started pprof server", "addr", pprofSrv.Addr()) cfg := NewConfig(cliCtx)
defer func() { if err := cfg.Check(); err != nil {
if err := pprofSrv.Stop(context.Background()); err != nil { return nil, fmt.Errorf("invalid CLI flags: %w", err)
l.Error("failed to stop pprof server", "err", err)
}
}()
}
metricsCfg := cfg.MetricsConfig
if metricsCfg.Enabled {
l.Debug("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
metricsSrv, err := m.Start(metricsCfg.ListenAddr, metricsCfg.ListenPort)
if err != nil {
return fmt.Errorf("failed to start metrics server: %w", err)
} }
l.Info("started metrics server", "addr", metricsSrv.Addr())
defer func() {
if err := metricsSrv.Stop(context.Background()); err != nil {
l.Error("failed to stop pprof server", "err", err)
}
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
m.StartBalanceMetrics(ctx, l, batchSubmitter.L1Client, batchSubmitter.TxManager.From())
}
server := oprpc.NewServer(
cfg.RPCFlag.ListenAddr,
cfg.RPCFlag.ListenPort,
version,
oprpc.WithLogger(l),
)
if cfg.RPCFlag.EnableAdmin {
adminAPI := rpc.NewAdminAPI(batchSubmitter, &m.RPCMetrics, l)
server.AddAPI(rpc.GetAdminAPI(adminAPI))
l.Info("Admin RPC enabled")
}
if err := server.Start(); err != nil {
return fmt.Errorf("error starting RPC server: %w", err)
}
m.RecordInfo(version) l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig)
m.RecordUp() oplog.SetGlobalLogHandler(l.GetHandler())
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
opio.BlockOnInterrupts() l.Info("Initializing Batch Submitter")
if err := server.Stop(); err != nil { return BatcherServiceFromCLIConfig(cliCtx.Context, version, cfg, l)
l.Error("Error shutting down http server: %w", err)
} }
return nil
} }
...@@ -3,52 +3,17 @@ package batcher ...@@ -3,52 +3,17 @@ package batcher
import ( import (
"time" "time"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/compressor"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
type Config struct {
log log.Logger
metr metrics.Metricer
L1Client *ethclient.Client
L2Client *ethclient.Client
RollupNode *sources.RollupClient
TxManager txmgr.TxManager
NetworkTimeout time.Duration
PollInterval time.Duration
MaxPendingTransactions uint64
// RollupConfig is queried at startup
Rollup *rollup.Config
// Channel builder parameters
Channel ChannelConfig
}
// Check ensures that the [Config] is valid.
func (c *Config) Check() error {
if err := c.Rollup.Check(); err != nil {
return err
}
if err := c.Channel.Check(); err != nil {
return err
}
return nil
}
type CLIConfig struct { type CLIConfig struct {
// L1EthRpc is the HTTP provider URL for L1. // L1EthRpc is the HTTP provider URL for L1.
L1EthRpc string L1EthRpc string
...@@ -92,11 +57,11 @@ type CLIConfig struct { ...@@ -92,11 +57,11 @@ type CLIConfig struct {
MetricsConfig opmetrics.CLIConfig MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig PprofConfig oppprof.CLIConfig
CompressorConfig compressor.CLIConfig CompressorConfig compressor.CLIConfig
RPCFlag oprpc.CLIConfig RPC oprpc.CLIConfig
} }
func (c CLIConfig) Check() error { func (c *CLIConfig) Check() error {
// TODO: check the sanity of flags loaded directly https://github.com/ethereum-optimism/optimism/issues/7512 // TODO(7512): check the sanity of flags loaded directly https://github.com/ethereum-optimism/optimism/issues/7512
if err := c.MetricsConfig.Check(); err != nil { if err := c.MetricsConfig.Check(); err != nil {
return err return err
...@@ -107,15 +72,15 @@ func (c CLIConfig) Check() error { ...@@ -107,15 +72,15 @@ func (c CLIConfig) Check() error {
if err := c.TxMgrConfig.Check(); err != nil { if err := c.TxMgrConfig.Check(); err != nil {
return err return err
} }
if err := c.RPCFlag.Check(); err != nil { if err := c.RPC.Check(); err != nil {
return err return err
} }
return nil return nil
} }
// NewConfig parses the Config from the provided flags or environment variables. // NewConfig parses the Config from the provided flags or environment variables.
func NewConfig(ctx *cli.Context) CLIConfig { func NewConfig(ctx *cli.Context) *CLIConfig {
return CLIConfig{ return &CLIConfig{
/* Required Flags */ /* Required Flags */
L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name), L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
L2EthRpc: ctx.String(flags.L2EthRpcFlag.Name), L2EthRpc: ctx.String(flags.L2EthRpcFlag.Name),
...@@ -133,6 +98,6 @@ func NewConfig(ctx *cli.Context) CLIConfig { ...@@ -133,6 +98,6 @@ func NewConfig(ctx *cli.Context) CLIConfig {
MetricsConfig: opmetrics.ReadCLIConfig(ctx), MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx), PprofConfig: oppprof.ReadCLIConfig(ctx),
CompressorConfig: compressor.ReadCLIConfig(ctx), CompressorConfig: compressor.ReadCLIConfig(ctx),
RPCFlag: oprpc.ReadCLIConfig(ctx), RPC: oprpc.ReadCLIConfig(ctx),
} }
} }
This diff is collapsed.
This diff is collapsed.
package main package main
import ( import (
"fmt"
"os" "os"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/batcher" "github.com/ethereum-optimism/optimism/op-batcher/batcher"
...@@ -26,11 +26,11 @@ func main() { ...@@ -26,11 +26,11 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Flags = cliapp.ProtectFlags(flags.Flags) app.Flags = cliapp.ProtectFlags(flags.Flags)
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate) app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "op-batcher" app.Name = "op-batcher"
app.Usage = "Batch Submitter Service" app.Usage = "Batch Submitter Service"
app.Description = "Service for generating and submitting L2 tx batches to L1" app.Description = "Service for generating and submitting L2 tx batches to L1"
app.Action = curryMain(Version) app.Action = cliapp.LifecycleCmd(batcher.Main(Version))
app.Commands = []*cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "doc", Name: "doc",
...@@ -43,11 +43,3 @@ func main() { ...@@ -43,11 +43,3 @@ func main() {
log.Crit("Application failed", "message", err) log.Crit("Application failed", "message", err)
} }
} }
// curryMain transforms the batcher.Main function into an app.Action
// This is done to capture the Version of the batcher.
func curryMain(version string) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
return batcher.Main(version, ctx)
}
}
package metrics package metrics
import ( import (
"context" "io"
"github.com/prometheus/client_golang/prometheus"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/httputil"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics" txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
) )
...@@ -28,6 +28,10 @@ type Metricer interface { ...@@ -28,6 +28,10 @@ type Metricer interface {
// Record Tx metrics // Record Tx metrics
txmetrics.TxMetricer txmetrics.TxMetricer
opmetrics.RPCMetricer
StartBalanceMetrics(l log.Logger, client *ethclient.Client, account common.Address) io.Closer
RecordLatestL1Block(l1ref eth.L1BlockRef) RecordLatestL1Block(l1ref eth.L1BlockRef)
RecordL2BlocksLoaded(l2ref eth.L2BlockRef) RecordL2BlocksLoaded(l2ref eth.L2BlockRef)
RecordChannelOpened(id derive.ChannelID, numPendingBlocks int) RecordChannelOpened(id derive.ChannelID, numPendingBlocks int)
...@@ -79,6 +83,9 @@ type Metrics struct { ...@@ -79,6 +83,9 @@ type Metrics struct {
var _ Metricer = (*Metrics)(nil) var _ Metricer = (*Metrics)(nil)
// implements the Registry getter, for metrics HTTP server to hook into
var _ opmetrics.RegistryMetricer = (*Metrics)(nil)
func NewMetrics(procName string) *Metrics { func NewMetrics(procName string) *Metrics {
if procName == "" { if procName == "" {
procName = "default" procName = "default"
...@@ -179,17 +186,16 @@ func NewMetrics(procName string) *Metrics { ...@@ -179,17 +186,16 @@ func NewMetrics(procName string) *Metrics {
} }
} }
func (m *Metrics) Start(host string, port int) (*httputil.HTTPServer, error) { func (m *Metrics) Registry() *prometheus.Registry {
return opmetrics.StartServer(m.registry, host, port) return m.registry
} }
func (m *Metrics) Document() []opmetrics.DocumentedMetric { func (m *Metrics) Document() []opmetrics.DocumentedMetric {
return m.factory.Document() return m.factory.Document()
} }
func (m *Metrics) StartBalanceMetrics(ctx context.Context, func (m *Metrics) StartBalanceMetrics(l log.Logger, client *ethclient.Client, account common.Address) io.Closer {
l log.Logger, client *ethclient.Client, account common.Address) { return opmetrics.LaunchBalanceMetrics(l, m.registry, m.ns, client, account)
opmetrics.LaunchBalanceMetrics(ctx, l, m.registry, m.ns, client, account)
} }
// RecordInfo sets a pseudo-metric that contains versioning and // RecordInfo sets a pseudo-metric that contains versioning and
......
package metrics package metrics
import ( import (
"io"
"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/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics" txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
"github.com/ethereum/go-ethereum/core/types"
) )
type noopMetrics struct { type noopMetrics struct {
opmetrics.NoopRefMetrics opmetrics.NoopRefMetrics
txmetrics.NoopTxMetrics txmetrics.NoopTxMetrics
opmetrics.NoopRPCMetrics
} }
var NoopMetrics Metricer = new(noopMetrics) var NoopMetrics Metricer = new(noopMetrics)
...@@ -35,3 +42,6 @@ func (*noopMetrics) RecordChannelTimedOut(derive.ChannelID) {} ...@@ -35,3 +42,6 @@ func (*noopMetrics) RecordChannelTimedOut(derive.ChannelID) {}
func (*noopMetrics) RecordBatchTxSubmitted() {} func (*noopMetrics) RecordBatchTxSubmitted() {}
func (*noopMetrics) RecordBatchTxSuccess() {} func (*noopMetrics) RecordBatchTxSuccess() {}
func (*noopMetrics) RecordBatchTxFailed() {} func (*noopMetrics) RecordBatchTxFailed() {}
func (*noopMetrics) StartBalanceMetrics(log.Logger, *ethclient.Client, common.Address) io.Closer {
return nil
}
...@@ -10,17 +10,17 @@ import ( ...@@ -10,17 +10,17 @@ import (
"github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/rpc"
) )
type batcherClient interface { type BatcherDriver interface {
Start() error StartBatchSubmitting() error
Stop(ctx context.Context) error StopBatchSubmitting(ctx context.Context) error
} }
type adminAPI struct { type adminAPI struct {
*rpc.CommonAdminAPI *rpc.CommonAdminAPI
b batcherClient b BatcherDriver
} }
func NewAdminAPI(dr batcherClient, m metrics.RPCMetricer, log log.Logger) *adminAPI { func NewAdminAPI(dr BatcherDriver, m metrics.RPCMetricer, log log.Logger) *adminAPI {
return &adminAPI{ return &adminAPI{
CommonAdminAPI: rpc.NewCommonAdminAPI(m, log), CommonAdminAPI: rpc.NewCommonAdminAPI(m, log),
b: dr, b: dr,
...@@ -35,9 +35,9 @@ func GetAdminAPI(api *adminAPI) gethrpc.API { ...@@ -35,9 +35,9 @@ func GetAdminAPI(api *adminAPI) gethrpc.API {
} }
func (a *adminAPI) StartBatcher(_ context.Context) error { func (a *adminAPI) StartBatcher(_ context.Context) error {
return a.b.Start() return a.b.StartBatchSubmitting()
} }
func (a *adminAPI) StopBatcher(ctx context.Context) error { func (a *adminAPI) StopBatcher(ctx context.Context) error {
return a.b.Stop(ctx) return a.b.StopBatchSubmitting(ctx)
} }
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"os" "os"
op_challenger "github.com/ethereum-optimism/optimism/op-challenger" op_challenger "github.com/ethereum-optimism/optimism/op-challenger"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -21,19 +22,7 @@ var ( ...@@ -21,19 +22,7 @@ var (
) )
// VersionWithMeta holds the textual version string including the metadata. // VersionWithMeta holds the textual version string including the metadata.
var VersionWithMeta = func() string { var VersionWithMeta = opservice.FormatVersion(version.Version, GitCommit, GitDate, version.Meta)
v := version.Version
if GitCommit != "" {
v += "-" + GitCommit[:8]
}
if GitDate != "" {
v += "-" + GitDate
}
if version.Meta != "" {
v += "-" + version.Meta
}
return v
}()
func main() { func main() {
args := os.Args args := os.Args
......
...@@ -34,7 +34,7 @@ type OutputTraceProvider struct { ...@@ -34,7 +34,7 @@ type OutputTraceProvider struct {
} }
func NewTraceProvider(ctx context.Context, logger log.Logger, rollupRpc string, gameDepth, prestateBlock, poststateBlock uint64) (*OutputTraceProvider, error) { func NewTraceProvider(ctx context.Context, logger log.Logger, rollupRpc string, gameDepth, prestateBlock, poststateBlock uint64) (*OutputTraceProvider, error) {
rollupClient, err := dial.DialRollupClientWithTimeout(dial.DefaultDialTimeout, logger, rollupRpc) rollupClient, err := dial.DialRollupClientWithTimeout(ctx, dial.DefaultDialTimeout, logger, rollupRpc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -55,7 +55,7 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se ...@@ -55,7 +55,7 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
return nil, fmt.Errorf("failed to create the transaction manager: %w", err) return nil, fmt.Errorf("failed to create the transaction manager: %w", err)
} }
l1Client, err := dial.DialEthClientWithTimeout(dial.DefaultDialTimeout, logger, cfg.L1EthRpc) l1Client, err := dial.DialEthClientWithTimeout(ctx, dial.DefaultDialTimeout, logger, cfg.L1EthRpc)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to dial L1: %w", err) return nil, fmt.Errorf("failed to dial L1: %w", err)
} }
......
...@@ -133,7 +133,12 @@ func (m *Metrics) StartBalanceMetrics( ...@@ -133,7 +133,12 @@ func (m *Metrics) StartBalanceMetrics(
client *ethclient.Client, client *ethclient.Client,
account common.Address, account common.Address,
) { ) {
opmetrics.LaunchBalanceMetrics(ctx, l, m.registry, m.ns, client, account) // TODO(7684): util was refactored to close, but ctx is still being used by caller for shutdown
balanceMetric := opmetrics.LaunchBalanceMetrics(l, m.registry, m.ns, client, account)
go func() {
<-ctx.Done()
_ = balanceMetric.Close()
}()
} }
// RecordInfo sets a pseudo-metric that contains versioning and // RecordInfo sets a pseudo-metric that contains versioning and
......
...@@ -127,9 +127,9 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) { ...@@ -127,9 +127,9 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) {
nextBlockTime := eth.Uint64Quantity(parent.Time) + 2 nextBlockTime := eth.Uint64Quantity(parent.Time) + 2
var w *eth.Withdrawals var w *types.Withdrawals
if sd.RollupCfg.IsCanyon(uint64(nextBlockTime)) { if sd.RollupCfg.IsCanyon(uint64(nextBlockTime)) {
w = &eth.Withdrawals{} w = &types.Withdrawals{}
} }
// Now let's ask the engine to build a block // Now let's ask the engine to build a block
......
package geth package geth
import ( import (
"math/rand"
"time" "time"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -12,6 +12,9 @@ import ( ...@@ -12,6 +12,9 @@ import (
"github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/testutils"
) )
// fakePoS is a testing-only utility to attach to Geth, // fakePoS is a testing-only utility to attach to Geth,
...@@ -22,6 +25,8 @@ type fakePoS struct { ...@@ -22,6 +25,8 @@ type fakePoS struct {
log log.Logger log log.Logger
blockTime uint64 blockTime uint64
withdrawalsIndex uint64
finalizedDistance uint64 finalizedDistance uint64
safeDistance uint64 safeDistance uint64
...@@ -33,6 +38,7 @@ func (f *fakePoS) Start() error { ...@@ -33,6 +38,7 @@ func (f *fakePoS) Start() error {
if advancing, ok := f.clock.(*clock.AdvancingClock); ok { if advancing, ok := f.clock.(*clock.AdvancingClock); ok {
advancing.Start() advancing.Start()
} }
withdrawalsRNG := rand.New(rand.NewSource(450368975843)) // avoid generating the same address as any test
f.sub = event.NewSubscription(func(quit <-chan struct{}) error { f.sub = event.NewSubscription(func(quit <-chan struct{}) error {
// poll every half a second: enough to catch up with any block time when ticks are missed // poll every half a second: enough to catch up with any block time when ticks are missed
t := f.clock.NewTicker(time.Second / 2) t := f.clock.NewTicker(time.Second / 2)
...@@ -64,6 +70,17 @@ func (f *fakePoS) Start() error { ...@@ -64,6 +70,17 @@ func (f *fakePoS) Start() error {
// We're a long way behind, let's skip some blocks... // We're a long way behind, let's skip some blocks...
newBlockTime = uint64(f.clock.Now().Unix()) newBlockTime = uint64(f.clock.Now().Unix())
} }
// create some random withdrawals
withdrawals := make([]*types.Withdrawal, withdrawalsRNG.Intn(4))
for i := 0; i < len(withdrawals); i++ {
withdrawals[i] = &types.Withdrawal{
Index: f.withdrawalsIndex + uint64(i),
Validator: withdrawalsRNG.Uint64() % 100_000_000, // 100 million fake validators
Address: testutils.RandomAddress(withdrawalsRNG),
// in gwei, consensus-layer quirk. withdraw non-zero value up to 50 ETH
Amount: uint64(withdrawalsRNG.Intn(50_000_000_000) + 1),
}
}
res, err := f.engineAPI.ForkchoiceUpdatedV2(engine.ForkchoiceStateV1{ res, err := f.engineAPI.ForkchoiceUpdatedV2(engine.ForkchoiceStateV1{
HeadBlockHash: head.Hash(), HeadBlockHash: head.Hash(),
SafeBlockHash: safe.Hash(), SafeBlockHash: safe.Hash(),
...@@ -72,7 +89,7 @@ func (f *fakePoS) Start() error { ...@@ -72,7 +89,7 @@ func (f *fakePoS) Start() error {
Timestamp: newBlockTime, Timestamp: newBlockTime,
Random: common.Hash{}, Random: common.Hash{},
SuggestedFeeRecipient: head.Coinbase, SuggestedFeeRecipient: head.Coinbase,
Withdrawals: make([]*types.Withdrawal, 0), Withdrawals: withdrawals,
}) })
if err != nil { if err != nil {
f.log.Error("failed to start building L1 block", "err", err) f.log.Error("failed to start building L1 block", "err", err)
...@@ -109,6 +126,10 @@ func (f *fakePoS) Start() error { ...@@ -109,6 +126,10 @@ func (f *fakePoS) Start() error {
f.log.Error("failed to make built L1 block canonical", "err", err) f.log.Error("failed to make built L1 block canonical", "err", err)
continue continue
} }
// Increment global withdrawals index in the CL.
// The EL doesn't really care about the value,
// but it's nice to mock something consistent with the CL specs.
f.withdrawalsIndex += uint64(len(withdrawals))
case <-quit: case <-quit:
return nil return nil
} }
......
...@@ -210,9 +210,9 @@ func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.Payloa ...@@ -210,9 +210,9 @@ func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.Payloa
txBytes = append(txBytes, bin) txBytes = append(txBytes, bin)
} }
var withdrawals *eth.Withdrawals var withdrawals *types.Withdrawals
if d.L2ChainConfig.IsCanyon(uint64(timestamp)) { if d.L2ChainConfig.IsCanyon(uint64(timestamp)) {
withdrawals = &eth.Withdrawals{} withdrawals = &types.Withdrawals{}
} }
attrs := eth.PayloadAttributes{ attrs := eth.PayloadAttributes{
......
...@@ -823,7 +823,7 @@ func TestCanyon(t *testing.T) { ...@@ -823,7 +823,7 @@ func TestCanyon(t *testing.T) {
b, err := opGeth.AddL2Block(ctx) b, err := opGeth.AddL2Block(ctx)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, *b.Withdrawals, eth.Withdrawals{}) assert.Equal(t, *b.Withdrawals, types.Withdrawals{})
l1Block, err := opGeth.L2Client.BlockByNumber(ctx, nil) l1Block, err := opGeth.L2Client.BlockByNumber(ctx, nil)
require.Nil(t, err) require.Nil(t, err)
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand" "crypto/rand"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"net" "net"
...@@ -37,7 +38,6 @@ import ( ...@@ -37,7 +38,6 @@ import (
bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" bss "github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/compressor"
batchermetrics "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/config"
...@@ -254,7 +254,7 @@ type System struct { ...@@ -254,7 +254,7 @@ type System struct {
RawClients map[string]*rpc.Client RawClients map[string]*rpc.Client
RollupNodes map[string]*rollupNode.OpNode RollupNodes map[string]*rollupNode.OpNode
L2OutputSubmitter *l2os.L2OutputSubmitter L2OutputSubmitter *l2os.L2OutputSubmitter
BatchSubmitter *bss.BatchSubmitter BatchSubmitter *bss.BatcherService
Mocknet mocknet.Mocknet Mocknet mocknet.Mocknet
// TimeTravelClock is nil unless SystemConfig.SupportL1TimeTravel was set to true // TimeTravelClock is nil unless SystemConfig.SupportL1TimeTravel was set to true
...@@ -270,18 +270,16 @@ func (sys *System) NodeEndpoint(name string) string { ...@@ -270,18 +270,16 @@ func (sys *System) NodeEndpoint(name string) string {
} }
func (sys *System) Close() { func (sys *System) Close() {
postCtx, postCancel := context.WithCancel(context.Background())
postCancel() // immediate shutdown, no allowance for idling
if sys.L2OutputSubmitter != nil { if sys.L2OutputSubmitter != nil {
sys.L2OutputSubmitter.Stop() sys.L2OutputSubmitter.Stop()
} }
if sys.BatchSubmitter != nil { if sys.BatchSubmitter != nil {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) _ = sys.BatchSubmitter.Kill()
defer cancel()
sys.BatchSubmitter.StopIfRunning(ctx)
} }
postCtx, postCancel := context.WithCancel(context.Background())
postCancel() // immediate shutdown, no allowance for idling
for _, node := range sys.RollupNodes { for _, node := range sys.RollupNodes {
_ = node.Stop(postCtx) _ = node.Stop(postCtx)
} }
...@@ -681,8 +679,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -681,8 +679,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
return nil, fmt.Errorf("unable to start l2 output submitter: %w", err) return nil, fmt.Errorf("unable to start l2 output submitter: %w", err)
} }
// Batch Submitter batcherCLIConfig := &bss.CLIConfig{
sys.BatchSubmitter, err = bss.NewBatchSubmitterFromCLIConfig(bss.CLIConfig{
L1EthRpc: sys.EthInstances["l1"].WSEndpoint(), L1EthRpc: sys.EthInstances["l1"].WSEndpoint(),
L2EthRpc: sys.EthInstances["sequencer"].WSEndpoint(), L2EthRpc: sys.EthInstances["sequencer"].WSEndpoint(),
RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(), RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(),
...@@ -701,17 +698,17 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -701,17 +698,17 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
Level: log.LvlInfo, Level: log.LvlInfo,
Format: oplog.FormatText, Format: oplog.FormatText,
}, },
}, sys.cfg.Loggers["batcher"], batchermetrics.NoopMetrics) Stopped: sys.cfg.DisableBatcher, // Batch submitter may be enabled later
}
// Batch Submitter
batcher, err := bss.BatcherServiceFromCLIConfig(context.Background(), "0.0.1", batcherCLIConfig, sys.cfg.Loggers["batcher"])
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to setup batch submitter: %w", err) return nil, fmt.Errorf("failed to setup batch submitter: %w", err)
} }
if err := batcher.Start(context.Background()); err != nil {
// Batcher may be enabled later return nil, errors.Join(fmt.Errorf("failed to start batch submitter: %w", err), batcher.Stop(context.Background()))
if !sys.cfg.DisableBatcher {
if err := sys.BatchSubmitter.Start(); err != nil {
return nil, fmt.Errorf("unable to start batch submitter: %w", err)
}
} }
sys.BatchSubmitter = batcher
return sys, nil return sys, nil
} }
......
...@@ -93,7 +93,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) { ...@@ -93,7 +93,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) {
l2OutputRoot := agreedL2Output.OutputRoot l2OutputRoot := agreedL2Output.OutputRoot
t.Log("=====Stopping batch submitter=====") t.Log("=====Stopping batch submitter=====")
err = sys.BatchSubmitter.Stop(ctx) err = sys.BatchSubmitter.Driver().StopBatchSubmitting(ctx)
require.NoError(t, err, "could not stop batch submitter") require.NoError(t, err, "could not stop batch submitter")
// Wait for the sequencer to catch up with the current L1 head so we know all submitted batches are processed // Wait for the sequencer to catch up with the current L1 head so we know all submitted batches are processed
...@@ -121,7 +121,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) { ...@@ -121,7 +121,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) {
l2Claim := l2Output.OutputRoot l2Claim := l2Output.OutputRoot
t.Log("=====Restarting batch submitter=====") t.Log("=====Restarting batch submitter=====")
err = sys.BatchSubmitter.Start() err = sys.BatchSubmitter.Driver().StartBatchSubmitting()
require.NoError(t, err, "could not start batch submitter") require.NoError(t, err, "could not start batch submitter")
t.Log("Add a transaction to the next batch after sequence of empty blocks") t.Log("Add a transaction to the next batch after sequence of empty blocks")
...@@ -258,7 +258,7 @@ func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *Syste ...@@ -258,7 +258,7 @@ func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *Syste
t.Log("Shutting down network") t.Log("Shutting down network")
// Shutdown the nodes from the actual chain. Should now be able to run using only the pre-fetched data. // Shutdown the nodes from the actual chain. Should now be able to run using only the pre-fetched data.
sys.BatchSubmitter.StopIfRunning(context.Background()) require.NoError(t, sys.BatchSubmitter.Kill())
sys.L2OutputSubmitter.Stop() sys.L2OutputSubmitter.Stop()
sys.L2OutputSubmitter = nil sys.L2OutputSubmitter = nil
for _, node := range sys.EthInstances { for _, node := range sys.EthInstances {
......
...@@ -1266,7 +1266,7 @@ func TestStopStartBatcher(t *testing.T) { ...@@ -1266,7 +1266,7 @@ func TestStopStartBatcher(t *testing.T) {
require.Greater(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain did not advance") require.Greater(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain did not advance")
// stop the batch submission // stop the batch submission
err = sys.BatchSubmitter.Stop(context.Background()) err = sys.BatchSubmitter.Driver().StopBatchSubmitting(context.Background())
require.Nil(t, err) require.Nil(t, err)
// wait for any old safe blocks being submitted / derived // wait for any old safe blocks being submitted / derived
...@@ -1286,7 +1286,7 @@ func TestStopStartBatcher(t *testing.T) { ...@@ -1286,7 +1286,7 @@ func TestStopStartBatcher(t *testing.T) {
require.Equal(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain advanced while batcher was stopped") require.Equal(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain advanced while batcher was stopped")
// start the batch submission // start the batch submission
err = sys.BatchSubmitter.Start() err = sys.BatchSubmitter.Driver().StartBatchSubmitting()
require.Nil(t, err) require.Nil(t, err)
time.Sleep(safeBlockInclusionDuration) time.Sleep(safeBlockInclusionDuration)
...@@ -1325,7 +1325,7 @@ func TestBatcherMultiTx(t *testing.T) { ...@@ -1325,7 +1325,7 @@ func TestBatcherMultiTx(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
// start batch submission // start batch submission
err = sys.BatchSubmitter.Start() err = sys.BatchSubmitter.Driver().StartBatchSubmitting()
require.Nil(t, err) require.Nil(t, err)
totalTxCount := 0 totalTxCount := 0
......
package main package main
import ( import (
"fmt"
"os" "os"
heartbeat "github.com/ethereum-optimism/optimism/op-heartbeat" heartbeat "github.com/ethereum-optimism/optimism/op-heartbeat"
"github.com/ethereum-optimism/optimism/op-heartbeat/flags" "github.com/ethereum-optimism/optimism/op-heartbeat/flags"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -22,7 +22,7 @@ func main() { ...@@ -22,7 +22,7 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Flags = flags.Flags app.Flags = flags.Flags
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate) app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "op-heartbeat" app.Name = "op-heartbeat"
app.Usage = "Heartbeat recorder" app.Usage = "Heartbeat recorder"
app.Description = "Service that records opt-in heartbeats from op nodes" app.Description = "Service that records opt-in heartbeats from op nodes"
......
...@@ -29,19 +29,7 @@ var ( ...@@ -29,19 +29,7 @@ var (
) )
// VersionWithMeta holds the textual version string including the metadata. // VersionWithMeta holds the textual version string including the metadata.
var VersionWithMeta = func() string { var VersionWithMeta = opservice.FormatVersion(version.Version, GitCommit, GitDate, version.Meta)
v := version.Version
if GitCommit != "" {
v += "-" + GitCommit[:8]
}
if GitDate != "" {
v += "-" + GitDate
}
if version.Meta != "" {
v += "-" + version.Meta
}
return v
}()
func main() { func main() {
// Set up logger with a default INFO level in case we fail to parse flags, // Set up logger with a default INFO level in case we fail to parse flags,
......
...@@ -604,7 +604,9 @@ func (m *Metrics) ReportProtocolVersions(local, engine, recommended, required pa ...@@ -604,7 +604,9 @@ func (m *Metrics) ReportProtocolVersions(local, engine, recommended, required pa
m.ProtocolVersions.WithLabelValues(local.String(), engine.String(), recommended.String(), required.String()).Set(1) m.ProtocolVersions.WithLabelValues(local.String(), engine.String(), recommended.String(), required.String()).Set(1)
} }
type noopMetricer struct{} type noopMetricer struct {
metrics.NoopRPCMetrics
}
var NoopMetrics Metricer = new(noopMetricer) var NoopMetrics Metricer = new(noopMetricer)
...@@ -614,17 +616,6 @@ func (n *noopMetricer) RecordInfo(version string) { ...@@ -614,17 +616,6 @@ func (n *noopMetricer) RecordInfo(version string) {
func (n *noopMetricer) RecordUp() { func (n *noopMetricer) RecordUp() {
} }
func (n *noopMetricer) RecordRPCServerRequest(method string) func() {
return func() {}
}
func (n *noopMetricer) RecordRPCClientRequest(method string) func(err error) {
return func(err error) {}
}
func (n *noopMetricer) RecordRPCClientResponse(method string, err error) {
}
func (n *noopMetricer) SetDerivationIdle(status bool) { func (n *noopMetricer) SetDerivationIdle(status bool) {
} }
......
...@@ -109,9 +109,9 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex ...@@ -109,9 +109,9 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
txs = append(txs, l1InfoTx) txs = append(txs, l1InfoTx)
txs = append(txs, depositTxs...) txs = append(txs, depositTxs...)
var withdrawals *eth.Withdrawals var withdrawals *types.Withdrawals
if ba.cfg.IsCanyon(nextL2Time) { if ba.cfg.IsCanyon(nextL2Time) {
withdrawals = &eth.Withdrawals{} withdrawals = &types.Withdrawals{}
} }
return &eth.PayloadAttributes{ return &eth.PayloadAttributes{
......
...@@ -47,7 +47,7 @@ func AttributesMatchBlock(attrs *eth.PayloadAttributes, parentHash common.Hash, ...@@ -47,7 +47,7 @@ func AttributesMatchBlock(attrs *eth.PayloadAttributes, parentHash common.Hash,
return nil return nil
} }
func checkWithdrawalsMatch(attrWithdrawals *eth.Withdrawals, blockWithdrawals *eth.Withdrawals) error { func checkWithdrawalsMatch(attrWithdrawals *types.Withdrawals, blockWithdrawals *types.Withdrawals) error {
if attrWithdrawals == nil && blockWithdrawals == nil { if attrWithdrawals == nil && blockWithdrawals == nil {
return nil return nil
} }
...@@ -67,7 +67,7 @@ func checkWithdrawalsMatch(attrWithdrawals *eth.Withdrawals, blockWithdrawals *e ...@@ -67,7 +67,7 @@ func checkWithdrawalsMatch(attrWithdrawals *eth.Withdrawals, blockWithdrawals *e
for idx, expected := range *attrWithdrawals { for idx, expected := range *attrWithdrawals {
actual := (*blockWithdrawals)[idx] actual := (*blockWithdrawals)[idx]
if expected != actual { if *expected != *actual {
return fmt.Errorf("expected withdrawal %d to be %v, actual %v", idx, expected, actual) return fmt.Errorf("expected withdrawal %d to be %v, actual %v", idx, expected, actual)
} }
} }
......
...@@ -3,14 +3,15 @@ package derive ...@@ -3,14 +3,15 @@ package derive
import ( import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/core/types"
) )
func TestWithdrawalsMatch(t *testing.T) { func TestWithdrawalsMatch(t *testing.T) {
tests := []struct { tests := []struct {
attrs *eth.Withdrawals attrs *types.Withdrawals
block *eth.Withdrawals block *types.Withdrawals
shouldMatch bool shouldMatch bool
}{ }{
{ {
...@@ -19,36 +20,36 @@ func TestWithdrawalsMatch(t *testing.T) { ...@@ -19,36 +20,36 @@ func TestWithdrawalsMatch(t *testing.T) {
shouldMatch: true, shouldMatch: true,
}, },
{ {
attrs: &eth.Withdrawals{}, attrs: &types.Withdrawals{},
block: nil, block: nil,
shouldMatch: false, shouldMatch: false,
}, },
{ {
attrs: nil, attrs: nil,
block: &eth.Withdrawals{}, block: &types.Withdrawals{},
shouldMatch: false, shouldMatch: false,
}, },
{ {
attrs: &eth.Withdrawals{}, attrs: &types.Withdrawals{},
block: &eth.Withdrawals{}, block: &types.Withdrawals{},
shouldMatch: true, shouldMatch: true,
}, },
{ {
attrs: &eth.Withdrawals{ attrs: &types.Withdrawals{
{ {
Index: 1, Index: 1,
}, },
}, },
block: &eth.Withdrawals{}, block: &types.Withdrawals{},
shouldMatch: false, shouldMatch: false,
}, },
{ {
attrs: &eth.Withdrawals{ attrs: &types.Withdrawals{
{ {
Index: 1, Index: 1,
}, },
}, },
block: &eth.Withdrawals{ block: &types.Withdrawals{
{ {
Index: 2, Index: 2,
}, },
......
...@@ -8,6 +8,8 @@ LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-program/version.Vers ...@@ -8,6 +8,8 @@ LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-program/version.Vers
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-program/version.Meta=$(VERSION_META) LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-program/version.Meta=$(VERSION_META)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)" LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
COMPAT_DIR := temp/compat
op-program: \ op-program: \
op-program-host \ op-program-host \
op-program-client \ op-program-client \
...@@ -25,13 +27,26 @@ op-program-client-mips: ...@@ -25,13 +27,26 @@ op-program-client-mips:
# result is mips32, big endian, R3000 # result is mips32, big endian, R3000
clean: clean:
rm -rf bin rm -rf bin "$(COMPAT_DIR)"
test: test:
go test -v ./... go test -v ./...
verify-goerli: op-program-host op-program-client verify-goerli: op-program-host op-program-client
env GO111MODULE=on go run ./verify/cmd/goerli.go $$L1URL $$L2URL env GO111MODULE=on go run ./verify/cmd/goerli.go --l1 $$L1URL --l2 $$L2URL
capture-goerli-verify: op-program-host op-program-client
rm -rf "$(COMPAT_DIR)/goerli" "$(COMPAT_DIR)/goerli.tar.bz"
env GO111MODULE=on go run ./verify/cmd/goerli.go --l1 $$L1URL --l2 $$L2URL --datadir "$(COMPAT_DIR)/goerli"
tar jcf "$(COMPAT_DIR)/goerli.tar.bz" -C "$(COMPAT_DIR)" goerli
capture-chain-test-data: capture-goerli-verify
run-goerli-verify: op-program-host op-program-client
mkdir -p "$(COMPAT_DIR)"
curl -L -o "$(COMPAT_DIR)/goerli.tar.bz" https://github.com/ethereum-optimism/chain-test-data/releases/download/2023-10-11/goerli.tar.bz
tar jxf "$(COMPAT_DIR)/goerli.tar.bz" -C "$(COMPAT_DIR)"
./bin/op-program `cat "$(COMPAT_DIR)/goerli/args.txt"`
.PHONY: \ .PHONY: \
op-program \ op-program \
......
...@@ -57,9 +57,9 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi. ...@@ -57,9 +57,9 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
nextBlockTime := eth.Uint64Quantity(genesis.Time + 1) nextBlockTime := eth.Uint64Quantity(genesis.Time + 1)
var w *eth.Withdrawals var w *types.Withdrawals
if api.backend.Config().IsCanyon(uint64(nextBlockTime)) { if api.backend.Config().IsCanyon(uint64(nextBlockTime)) {
w = &eth.Withdrawals{} w = &types.Withdrawals{}
} }
result, err := api.engine.ForkchoiceUpdatedV2(api.ctx, &eth.ForkchoiceState{ result, err := api.engine.ForkchoiceUpdatedV2(api.ctx, &eth.ForkchoiceState{
...@@ -111,9 +111,9 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi. ...@@ -111,9 +111,9 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
t.Run("RejectInvalidBlockHash", func(t *testing.T) { t.Run("RejectInvalidBlockHash", func(t *testing.T) {
api := newTestHelper(t, createBackend) api := newTestHelper(t, createBackend)
var w *eth.Withdrawals var w *types.Withdrawals
if api.backend.Config().IsCanyon(uint64(0)) { if api.backend.Config().IsCanyon(uint64(0)) {
w = &eth.Withdrawals{} w = &types.Withdrawals{}
} }
// Invalid because BlockHash won't be correct (among many other reasons) // Invalid because BlockHash won't be correct (among many other reasons)
...@@ -385,9 +385,9 @@ func (h *testHelper) startBlockBuilding(head *types.Header, newBlockTimestamp et ...@@ -385,9 +385,9 @@ func (h *testHelper) startBlockBuilding(head *types.Header, newBlockTimestamp et
} }
canyonTime := h.backend.Config().CanyonTime canyonTime := h.backend.Config().CanyonTime
var w *eth.Withdrawals var w *types.Withdrawals
if canyonTime != nil && *canyonTime <= uint64(newBlockTimestamp) { if canyonTime != nil && *canyonTime <= uint64(newBlockTimestamp) {
w = &eth.Withdrawals{} w = &types.Withdrawals{}
} }
result, err := h.engine.ForkchoiceUpdatedV2(h.ctx, &eth.ForkchoiceState{ result, err := h.engine.ForkchoiceUpdatedV2(h.ctx, &eth.ForkchoiceState{
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum-optimism/optimism/op-program/host/flags" "github.com/ethereum-optimism/optimism/op-program/host/flags"
"github.com/ethereum-optimism/optimism/op-program/host/version" "github.com/ethereum-optimism/optimism/op-program/host/version"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -18,19 +19,7 @@ var ( ...@@ -18,19 +19,7 @@ var (
) )
// VersionWithMeta holds the textual version string including the metadata. // VersionWithMeta holds the textual version string including the metadata.
var VersionWithMeta = func() string { var VersionWithMeta = opservice.FormatVersion(version.Version, GitCommit, GitDate, version.Meta)
v := version.Version
if GitCommit != "" {
v += "-" + GitCommit[:8]
}
if GitDate != "" {
v += "-" + GitDate
}
if version.Meta != "" {
v += "-" + version.Meta
}
return v
}()
func main() { func main() {
args := os.Args args := os.Args
......
...@@ -2,10 +2,13 @@ package main ...@@ -2,10 +2,13 @@ package main
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strings"
"time" "time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
...@@ -15,28 +18,32 @@ import ( ...@@ -15,28 +18,32 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
const agreedBlockTrailingDistance = 100
func main() { func main() {
if len(os.Args) < 3 { var l1RpcUrl string
_, _ = fmt.Fprintln(os.Stderr, "Must specify L1 RPC URL and L2 RPC URL as arguments") var l1RpcKind string
var l2RpcUrl string
var dataDir string
flag.StringVar(&l1RpcUrl, "l1", "", "L1 RPC URL to use")
flag.StringVar(&l1RpcKind, "l1-rpckind", "alchemy", "L1 RPC kind")
flag.StringVar(&l2RpcUrl, "l2", "", "L2 RPC URL to use")
flag.StringVar(&dataDir, "datadir", "",
"Directory to use for storing pre-images. If not set a temporary directory will be used.")
flag.Parse()
if l1RpcUrl == "" || l2RpcUrl == "" {
_, _ = fmt.Fprintln(os.Stderr, "Must specify --l1 and --l2 RPC URLs")
os.Exit(2) os.Exit(2)
} }
l1RpcUrl := os.Args[1]
l2RpcUrl := os.Args[2]
l1RpcKind := "alchemy"
if len(os.Args) > 3 {
l1RpcKind = os.Args[3]
}
goerliOutputAddress := common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0") goerliOutputAddress := common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0")
err := Run(l1RpcUrl, l1RpcKind, l2RpcUrl, goerliOutputAddress) err := Run(l1RpcUrl, l1RpcKind, l2RpcUrl, goerliOutputAddress, dataDir)
if err != nil { if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed: %v\n", err.Error()) _, _ = fmt.Fprintf(os.Stderr, "Failed: %v\n", err.Error())
os.Exit(1) os.Exit(1)
} }
} }
func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common.Address) error { func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common.Address, dataDir string) error {
ctx := context.Background() ctx := context.Background()
l1RpcClient, err := rpc.Dial(l1RpcUrl) l1RpcClient, err := rpc.Dial(l1RpcUrl)
if err != nil { if err != nil {
...@@ -55,14 +62,7 @@ func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common ...@@ -55,14 +62,7 @@ func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common
return fmt.Errorf("create output oracle bindings: %w", err) return fmt.Errorf("create output oracle bindings: %w", err)
} }
// Find L2 finalized head. This is far enough back that we know it's submitted to L1 and won't be re-orged // Find L1 finalized block. Can't be re-orged.
l2FinalizedHead, err := l2Client.BlockByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
if err != nil {
return fmt.Errorf("get l2 safe head: %w", err)
}
fmt.Printf("Found L2 finalized head number: %v hash: %v\n", l2FinalizedHead.NumberU64(), l2FinalizedHead.Hash())
// Find L1 finalized block. Can't be re-orged and must contain all batches for the L2 finalized block
l1BlockNum := big.NewInt(int64(rpc.FinalizedBlockNumber)) l1BlockNum := big.NewInt(int64(rpc.FinalizedBlockNumber))
l1HeadBlock, err := l1Client.BlockByNumber(ctx, l1BlockNum) l1HeadBlock, err := l1Client.BlockByNumber(ctx, l1BlockNum)
if err != nil { if err != nil {
...@@ -70,89 +70,68 @@ func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common ...@@ -70,89 +70,68 @@ func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common
} }
fmt.Printf("Found l1 head block number: %v hash: %v\n", l1HeadBlock.NumberU64(), l1HeadBlock.Hash()) fmt.Printf("Found l1 head block number: %v hash: %v\n", l1HeadBlock.NumberU64(), l1HeadBlock.Hash())
// Get the most published L2 output from before the finalized block l1CallOpts := &bind.CallOpts{Context: ctx, BlockNumber: l1BlockNum}
callOpts := &bind.CallOpts{Context: ctx}
outputIndex, err := outputOracle.GetL2OutputIndexAfter(callOpts, l2FinalizedHead.Number()) // Find the latest output root published in this finalized block
latestOutputIndex, err := outputOracle.LatestOutputIndex(l1CallOpts)
if err != nil { if err != nil {
fmt.Println("Failed to get output index after finalized block. Checking latest output", "finalized", l2FinalizedHead.Number(), "err", err) return fmt.Errorf("fetch latest output index: %w", err)
outputIndex, err = outputOracle.LatestOutputIndex(callOpts)
if err != nil {
return fmt.Errorf("get latest output index: %w", err)
}
} else {
outputIndex = outputIndex.Sub(outputIndex, big.NewInt(1))
} }
output, err := outputOracle.GetL2Output(callOpts, outputIndex) output, err := outputOracle.GetL2Output(l1CallOpts, latestOutputIndex)
if err != nil { if err != nil {
return fmt.Errorf("retrieve latest output: %w", err) return fmt.Errorf("fetch l2 output %v: %w", latestOutputIndex, err)
}
// Check we wound up with an output prior to the finalized block
if output.L2BlockNumber.Uint64() > l2FinalizedHead.NumberU64() {
return fmt.Errorf("selected output is after finalized head output block: %v, finalized block: %v", output.L2BlockNumber.Uint64(), l2FinalizedHead.NumberU64())
} }
l1Head := l1HeadBlock.Hash() // Use the previous output as the agreed starting point
l2Claim := common.Hash(output.OutputRoot) agreedOutput, err := outputOracle.GetL2Output(l1CallOpts, new(big.Int).Sub(latestOutputIndex, common.Big1))
l2BlockNumber := output.L2BlockNumber
// Use an agreed starting L2 block some distance before the block the output claim is from
agreedBlockNumber := uint64(0)
if l2BlockNumber.Uint64() > agreedBlockTrailingDistance {
agreedBlockNumber = l2BlockNumber.Uint64() - agreedBlockTrailingDistance
}
l2AgreedBlock, err := l2Client.BlockByNumber(ctx, big.NewInt(int64(agreedBlockNumber)))
if err != nil { if err != nil {
return fmt.Errorf("retrieve agreed l2 block: %w", err) return fmt.Errorf("fetch l2 output before %v: %w", latestOutputIndex, err)
}
agreedOutputIndex, err := outputOracle.GetL2OutputIndexAfter(callOpts, l2AgreedBlock.Number())
if err != nil {
return fmt.Errorf("failed to output index after agreed block")
}
// Find an output that differs from what is being claimed
var agreedOutput bindings.TypesOutputProposal
for {
agreedOutput, err = outputOracle.GetL2Output(callOpts, agreedOutputIndex)
if err != nil {
return fmt.Errorf("retrieve agreed output: %w", err)
}
if agreedOutput.OutputRoot != output.OutputRoot {
break
}
fmt.Printf("Output at %v equals output at finalized block. Continuing search...\n", agreedOutput.L2BlockNumber)
agreedOutputIndex.Sub(agreedOutputIndex, big.NewInt(1))
if agreedOutputIndex.Int64() < 0 {
return fmt.Errorf("failed to find an output different from finalized block output")
}
} }
l2BlockAtOutput, err := l2Client.BlockByNumber(ctx, agreedOutput.L2BlockNumber) l2BlockAtOutput, err := l2Client.BlockByNumber(ctx, agreedOutput.L2BlockNumber)
if err != nil { if err != nil {
return fmt.Errorf("retrieve agreed block: %w", err) return fmt.Errorf("retrieve agreed block: %w", err)
} }
l2Head := l2BlockAtOutput.Hash() l2Head := l2BlockAtOutput.Hash()
l2BlockNumber := output.L2BlockNumber
l2Claim := common.Hash(output.OutputRoot)
l1Head := l1HeadBlock.Hash()
temp, err := os.MkdirTemp("", "oracledata") if dataDir == "" {
if err != nil { dataDir, err = os.MkdirTemp("", "oracledata")
return fmt.Errorf("create temp dir: %w", err)
}
defer func() {
err := os.RemoveAll(temp)
if err != nil { if err != nil {
fmt.Println("Failed to remove temp dir:" + err.Error()) return fmt.Errorf("create temp dir: %w", err)
}
defer func() {
err := os.RemoveAll(dataDir)
if err != nil {
fmt.Println("Failed to remove temp dir:" + err.Error())
}
}()
} else {
if err := os.MkdirAll(dataDir, 0755); err != nil {
fmt.Printf("Could not create data directory %v: %v", dataDir, err)
os.Exit(1)
} }
}() }
fmt.Printf("Using temp dir: %s\n", temp) fmt.Printf("Using dir: %s\n", dataDir)
args := []string{ args := []string{
"--log.level", "DEBUG", "--log.level", "DEBUG",
"--network", "goerli", "--network", "goerli",
"--exec", "./bin/op-program-client", "--exec", "./bin/op-program-client",
"--datadir", temp, "--datadir", dataDir,
"--l1.head", l1Head.Hex(), "--l1.head", l1Head.Hex(),
"--l2.head", l2Head.Hex(), "--l2.head", l2Head.Hex(),
"--l2.outputroot", common.Bytes2Hex(agreedOutput.OutputRoot[:]), "--l2.outputroot", common.Bytes2Hex(agreedOutput.OutputRoot[:]),
"--l2.claim", l2Claim.Hex(), "--l2.claim", l2Claim.Hex(),
"--l2.blocknumber", l2BlockNumber.String(), "--l2.blocknumber", l2BlockNumber.String(),
} }
fmt.Printf("Configuration: %s\n", args) argsStr := strings.Join(args, " ")
if err := os.WriteFile(filepath.Join(dataDir, "args.txt"), []byte(argsStr), 0644); err != nil {
fmt.Printf("Could not write args: %v", err)
os.Exit(1)
}
fmt.Printf("Configuration: %s\n", argsStr)
fmt.Println("Running in online mode") fmt.Println("Running in online mode")
err = runFaultProofProgram(ctx, append(args, "--l1", l1RpcUrl, "--l2", l2RpcUrl, "--l1.rpckind", l1RpcKind)) err = runFaultProofProgram(ctx, append(args, "--l1", l1RpcUrl, "--l2", l2RpcUrl, "--l1.rpckind", l1RpcKind))
if err != nil { if err != nil {
......
package main package main
import ( import (
"fmt"
"os" "os"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-proposer/flags" "github.com/ethereum-optimism/optimism/op-proposer/flags"
...@@ -26,7 +26,7 @@ func main() { ...@@ -26,7 +26,7 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Flags = cliapp.ProtectFlags(flags.Flags) app.Flags = cliapp.ProtectFlags(flags.Flags)
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate) app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "op-proposer" app.Name = "op-proposer"
app.Usage = "L2Output Submitter" app.Usage = "L2Output Submitter"
app.Description = "Service for generating and submitting L2 Output checkpoints to the L2OutputOracle contract" app.Description = "Service for generating and submitting L2 Output checkpoints to the L2OutputOracle contract"
......
...@@ -84,7 +84,12 @@ func (m *Metrics) Start(host string, port int) (*httputil.HTTPServer, error) { ...@@ -84,7 +84,12 @@ func (m *Metrics) Start(host string, port int) (*httputil.HTTPServer, error) {
func (m *Metrics) StartBalanceMetrics(ctx context.Context, func (m *Metrics) StartBalanceMetrics(ctx context.Context,
l log.Logger, client *ethclient.Client, account common.Address) { l log.Logger, client *ethclient.Client, account common.Address) {
opmetrics.LaunchBalanceMetrics(ctx, l, m.registry, m.ns, client, account) // TODO(7684): util was refactored to close, but ctx is still being used by caller for shutdown
balanceMetric := opmetrics.LaunchBalanceMetrics(l, m.registry, m.ns, client, account)
go func() {
<-ctx.Done()
_ = balanceMetric.Close()
}()
} }
// RecordInfo sets a pseudo-metric that contains versioning and // RecordInfo sets a pseudo-metric that contains versioning and
......
...@@ -172,12 +172,12 @@ func NewL2OutputSubmitterConfigFromCLIConfig(cfg CLIConfig, l log.Logger, m metr ...@@ -172,12 +172,12 @@ func NewL2OutputSubmitterConfigFromCLIConfig(cfg CLIConfig, l log.Logger, m metr
} }
// Connect to L1 and L2 providers. Perform these last since they are the most expensive. // Connect to L1 and L2 providers. Perform these last since they are the most expensive.
l1Client, err := dial.DialEthClientWithTimeout(dial.DefaultDialTimeout, l, cfg.L1EthRpc) l1Client, err := dial.DialEthClientWithTimeout(context.Background(), dial.DefaultDialTimeout, l, cfg.L1EthRpc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rollupClient, err := dial.DialRollupClientWithTimeout(dial.DefaultDialTimeout, l, cfg.RollupRpc) rollupClient, err := dial.DialRollupClientWithTimeout(context.Background(), dial.DefaultDialTimeout, l, cfg.RollupRpc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
package clock
import (
"context"
"sync"
"time"
)
// LoopFn is a simple ticker-loop with io.Closer support.
// Note that ticks adapt; slow function calls may result in lost ticks.
type LoopFn struct {
ctx context.Context
cancel context.CancelFunc
ticker Ticker
fn func(ctx context.Context)
onClose func() error
wg sync.WaitGroup
}
// Close cancels the context of the ongoing function call, waits for the call to complete, and cancels further calls.
// Close is safe to call again or concurrently. The onClose callback will be called for each Close call.
func (lf *LoopFn) Close() error {
lf.cancel() // stop any ongoing function call, and close the main loop
lf.wg.Wait() // wait for completion
if lf.onClose != nil {
return lf.onClose() // optional: user can specify function to close resources with
}
return nil
}
func (lf *LoopFn) work() {
defer lf.wg.Done()
defer lf.ticker.Stop() // clean up the timer
for {
select {
case <-lf.ctx.Done():
return
case <-lf.ticker.Ch():
ctx, cancel := context.WithCancel(lf.ctx)
func() {
defer cancel()
lf.fn(ctx)
}()
}
}
}
// NewLoopFn creates a periodic function call, which can be closed,
// with an optional onClose callback to clean up resources.
func NewLoopFn(clock Clock, fn func(ctx context.Context), onClose func() error, interval time.Duration) *LoopFn {
ctx, cancel := context.WithCancel(context.Background())
lf := &LoopFn{
ctx: ctx,
cancel: cancel,
fn: fn,
ticker: clock.NewTicker(interval),
onClose: onClose,
}
lf.wg.Add(1)
go lf.work()
return lf
}
package clock
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestLoopFn(t *testing.T) {
cl := NewDeterministicClock(time.Now())
calls := make(chan struct{}, 10)
testErr := errors.New("test close error")
loopFn := NewLoopFn(cl, func(ctx context.Context) {
calls <- struct{}{}
}, func() error {
close(calls)
return testErr
}, time.Second*10)
cl.AdvanceTime(time.Second * 15)
<-calls
cl.AdvanceTime(time.Second * 10)
<-calls
select {
case <-calls:
t.Fatal("more calls than expected")
default:
}
require.ErrorIs(t, loopFn.Close(), testErr)
}
...@@ -21,8 +21,8 @@ const defaultRetryTime = 2 * time.Second ...@@ -21,8 +21,8 @@ const defaultRetryTime = 2 * time.Second
// DialEthClientWithTimeout attempts to dial the L1 provider using the provided // DialEthClientWithTimeout attempts to dial the L1 provider using the provided
// URL. If the dial doesn't complete within defaultDialTimeout seconds, this // URL. If the dial doesn't complete within defaultDialTimeout seconds, this
// method will return an error. // method will return an error.
func DialEthClientWithTimeout(timeout time.Duration, log log.Logger, url string) (*ethclient.Client, error) { func DialEthClientWithTimeout(ctx context.Context, timeout time.Duration, log log.Logger, url string) (*ethclient.Client, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
c, err := dialRPCClientWithBackoff(ctx, log, url) c, err := dialRPCClientWithBackoff(ctx, log, url)
...@@ -35,8 +35,8 @@ func DialEthClientWithTimeout(timeout time.Duration, log log.Logger, url string) ...@@ -35,8 +35,8 @@ func DialEthClientWithTimeout(timeout time.Duration, log log.Logger, url string)
// DialRollupClientWithTimeout attempts to dial the RPC provider using the provided URL. // DialRollupClientWithTimeout attempts to dial the RPC provider using the provided URL.
// If the dial doesn't complete within timeout seconds, this method will return an error. // If the dial doesn't complete within timeout seconds, this method will return an error.
func DialRollupClientWithTimeout(timeout time.Duration, log log.Logger, url string) (*sources.RollupClient, error) { func DialRollupClientWithTimeout(ctx context.Context, timeout time.Duration, log log.Logger, url string) (*sources.RollupClient, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
rpcCl, err := dialRPCClientWithBackoff(ctx, log, url) rpcCl, err := dialRPCClientWithBackoff(ctx, log, url)
......
...@@ -7,6 +7,8 @@ import ( ...@@ -7,6 +7,8 @@ import (
"io" "io"
"math" "math"
"sync" "sync"
"github.com/ethereum/go-ethereum/core/types"
) )
type BlockVersion int type BlockVersion int
...@@ -180,16 +182,16 @@ func (payload *ExecutionPayload) MarshalSSZ(w io.Writer) (n int, err error) { ...@@ -180,16 +182,16 @@ func (payload *ExecutionPayload) MarshalSSZ(w io.Writer) (n int, err error) {
offset += transactionSize offset += transactionSize
// dyanmic value 3: Withdrawals // dyanmic value 3: Withdrawals
if payload.Withdrawals != nil { if payload.Withdrawals != nil {
marshalWithdrawals(buf[offset:], payload.Withdrawals) marshalWithdrawals(buf[offset:], *payload.Withdrawals)
} }
return w.Write(buf) return w.Write(buf)
} }
func marshalWithdrawals(out []byte, withdrawals *Withdrawals) { func marshalWithdrawals(out []byte, withdrawals types.Withdrawals) {
offset := uint32(0) offset := uint32(0)
for _, withdrawal := range *withdrawals { for _, withdrawal := range withdrawals {
binary.LittleEndian.PutUint64(out[offset:offset+8], withdrawal.Index) binary.LittleEndian.PutUint64(out[offset:offset+8], withdrawal.Index)
offset += 8 offset += 8
binary.LittleEndian.PutUint64(out[offset:offset+8], withdrawal.Validator) binary.LittleEndian.PutUint64(out[offset:offset+8], withdrawal.Validator)
...@@ -305,14 +307,14 @@ func (payload *ExecutionPayload) UnmarshalSSZ(version BlockVersion, scope uint32 ...@@ -305,14 +307,14 @@ func (payload *ExecutionPayload) UnmarshalSSZ(version BlockVersion, scope uint32
if err != nil { if err != nil {
return fmt.Errorf("failed to unmarshal withdrawals list: %w", err) return fmt.Errorf("failed to unmarshal withdrawals list: %w", err)
} }
payload.Withdrawals = withdrawals payload.Withdrawals = &withdrawals
} }
return nil return nil
} }
func unmarshalWithdrawals(in []byte) (*Withdrawals, error) { func unmarshalWithdrawals(in []byte) (types.Withdrawals, error) {
result := &Withdrawals{} result := types.Withdrawals{} // empty list by default, intentionally non-nil
if len(in)%withdrawalSize != 0 { if len(in)%withdrawalSize != 0 {
return nil, errors.New("invalid withdrawals data") return nil, errors.New("invalid withdrawals data")
...@@ -327,7 +329,7 @@ func unmarshalWithdrawals(in []byte) (*Withdrawals, error) { ...@@ -327,7 +329,7 @@ func unmarshalWithdrawals(in []byte) (*Withdrawals, error) {
offset := 0 offset := 0
for i := 0; i < withdrawalCount; i++ { for i := 0; i < withdrawalCount; i++ {
withdrawal := Withdrawal{} withdrawal := &types.Withdrawal{}
withdrawal.Index = binary.LittleEndian.Uint64(in[offset : offset+8]) withdrawal.Index = binary.LittleEndian.Uint64(in[offset : offset+8])
offset += 8 offset += 8
...@@ -341,7 +343,7 @@ func unmarshalWithdrawals(in []byte) (*Withdrawals, error) { ...@@ -341,7 +343,7 @@ func unmarshalWithdrawals(in []byte) (*Withdrawals, error) {
withdrawal.Amount = binary.LittleEndian.Uint64(in[offset : offset+8]) withdrawal.Amount = binary.LittleEndian.Uint64(in[offset : offset+8])
offset += 8 offset += 8
*result = append(*result, withdrawal) result = append(result, withdrawal)
} }
return result, nil return result, nil
......
...@@ -7,10 +7,12 @@ import ( ...@@ -7,10 +7,12 @@ import (
"math" "math"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
) )
// FuzzExecutionPayloadUnmarshal checks that our SSZ decoding never panics // FuzzExecutionPayloadUnmarshal checks that our SSZ decoding never panics
...@@ -138,9 +140,9 @@ func FuzzExecutionPayloadMarshalUnmarshalV2(f *testing.F) { ...@@ -138,9 +140,9 @@ func FuzzExecutionPayloadMarshalUnmarshalV2(f *testing.F) {
} }
wCount = wCount % maxWithdrawalsPerPayload wCount = wCount % maxWithdrawalsPerPayload
withdrawals := make(Withdrawals, wCount) withdrawals := make(types.Withdrawals, wCount)
for i := 0; i < int(wCount); i++ { for i := 0; i < int(wCount); i++ {
withdrawals[i] = Withdrawal{ withdrawals[i] = &types.Withdrawal{
Index: a, Index: a,
Validator: b, Validator: b,
Address: common.BytesToAddress(data[:20]), Address: common.BytesToAddress(data[:20]),
...@@ -228,10 +230,10 @@ func TestOPB04(t *testing.T) { ...@@ -228,10 +230,10 @@ func TestOPB04(t *testing.T) {
tests := []struct { tests := []struct {
version BlockVersion version BlockVersion
withdrawals *Withdrawals withdrawals *types.Withdrawals
}{ }{
{BlockV1, nil}, {BlockV1, nil},
{BlockV2, &Withdrawals{}}, {BlockV2, &types.Withdrawals{}},
} }
for _, test := range tests { for _, test := range tests {
...@@ -246,7 +248,7 @@ func TestOPB04(t *testing.T) { ...@@ -246,7 +248,7 @@ func TestOPB04(t *testing.T) {
} }
func createPayloadWithWithdrawals(w *Withdrawals) *ExecutionPayload { func createPayloadWithWithdrawals(w *types.Withdrawals) *ExecutionPayload {
return &ExecutionPayload{ return &ExecutionPayload{
ParentHash: common.HexToHash("0x123"), ParentHash: common.HexToHash("0x123"),
FeeRecipient: common.HexToAddress("0x456"), FeeRecipient: common.HexToAddress("0x456"),
...@@ -267,8 +269,8 @@ func createPayloadWithWithdrawals(w *Withdrawals) *ExecutionPayload { ...@@ -267,8 +269,8 @@ func createPayloadWithWithdrawals(w *Withdrawals) *ExecutionPayload {
} }
func TestMarshalUnmarshalWithdrawals(t *testing.T) { func TestMarshalUnmarshalWithdrawals(t *testing.T) {
emptyWithdrawal := &Withdrawals{} emptyWithdrawal := &types.Withdrawals{}
withdrawals := &Withdrawals{ withdrawals := &types.Withdrawals{
{ {
Index: 987, Index: 987,
Validator: 654, Validator: 654,
...@@ -276,18 +278,18 @@ func TestMarshalUnmarshalWithdrawals(t *testing.T) { ...@@ -276,18 +278,18 @@ func TestMarshalUnmarshalWithdrawals(t *testing.T) {
Amount: 321, Amount: 321,
}, },
} }
maxWithdrawals := make(Withdrawals, maxWithdrawalsPerPayload) maxWithdrawals := make(types.Withdrawals, maxWithdrawalsPerPayload)
for i := 0; i < maxWithdrawalsPerPayload; i++ { for i := 0; i < maxWithdrawalsPerPayload; i++ {
maxWithdrawals[i] = Withdrawal{ maxWithdrawals[i] = &types.Withdrawal{
Index: 987, Index: 987,
Validator: 654, Validator: 654,
Address: common.HexToAddress("0x898"), Address: common.HexToAddress("0x898"),
Amount: 321, Amount: 321,
} }
} }
tooManyWithdrawals := make(Withdrawals, maxWithdrawalsPerPayload+1) tooManyWithdrawals := make(types.Withdrawals, maxWithdrawalsPerPayload+1)
for i := 0; i < maxWithdrawalsPerPayload+1; i++ { for i := 0; i < maxWithdrawalsPerPayload+1; i++ {
tooManyWithdrawals[i] = Withdrawal{ tooManyWithdrawals[i] = &types.Withdrawal{
Index: 987, Index: 987,
Validator: 654, Validator: 654,
Address: common.HexToAddress("0x898"), Address: common.HexToAddress("0x898"),
...@@ -299,7 +301,7 @@ func TestMarshalUnmarshalWithdrawals(t *testing.T) { ...@@ -299,7 +301,7 @@ func TestMarshalUnmarshalWithdrawals(t *testing.T) {
name string name string
version BlockVersion version BlockVersion
hasError bool hasError bool
withdrawals *Withdrawals withdrawals *types.Withdrawals
}{ }{
{"ZeroWithdrawalsSucceeds", BlockV2, false, emptyWithdrawal}, {"ZeroWithdrawalsSucceeds", BlockV2, false, emptyWithdrawal},
{"ZeroWithdrawalsFailsToDeserialize", BlockV1, true, emptyWithdrawal}, {"ZeroWithdrawalsFailsToDeserialize", BlockV1, true, emptyWithdrawal},
......
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
...@@ -143,7 +142,8 @@ type ExecutionPayload struct { ...@@ -143,7 +142,8 @@ type ExecutionPayload struct {
ExtraData BytesMax32 `json:"extraData"` ExtraData BytesMax32 `json:"extraData"`
BaseFeePerGas Uint256Quantity `json:"baseFeePerGas"` BaseFeePerGas Uint256Quantity `json:"baseFeePerGas"`
BlockHash common.Hash `json:"blockHash"` BlockHash common.Hash `json:"blockHash"`
Withdrawals *Withdrawals `json:"withdrawals,omitempty"` // nil if not present, pre-shanghai
Withdrawals *types.Withdrawals `json:"withdrawals,omitempty"`
// Array of transaction objects, each object is a byte list (DATA) representing // Array of transaction objects, each object is a byte list (DATA) representing
// TransactionType || TransactionPayload or LegacyTransaction as defined in EIP-2718 // TransactionType || TransactionPayload or LegacyTransaction as defined in EIP-2718
Transactions []Data `json:"transactions"` Transactions []Data `json:"transactions"`
...@@ -237,7 +237,7 @@ func BlockAsPayload(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayload, ...@@ -237,7 +237,7 @@ func BlockAsPayload(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayload,
} }
if canyonForkTime != nil && uint64(payload.Timestamp) >= *canyonForkTime { if canyonForkTime != nil && uint64(payload.Timestamp) >= *canyonForkTime {
payload.Withdrawals = &Withdrawals{} payload.Withdrawals = &types.Withdrawals{}
} }
return payload, nil return payload, nil
...@@ -251,7 +251,7 @@ type PayloadAttributes struct { ...@@ -251,7 +251,7 @@ type PayloadAttributes struct {
// suggested value for the coinbase field of the new payload // suggested value for the coinbase field of the new payload
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient"`
// Withdrawals to include into the block -- should be nil or empty depending on Shanghai enablement // Withdrawals to include into the block -- should be nil or empty depending on Shanghai enablement
Withdrawals *Withdrawals `json:"withdrawals,omitempty"` Withdrawals *types.Withdrawals `json:"withdrawals,omitempty"`
// Transactions to force into the block (always at the start of the transactions list). // Transactions to force into the block (always at the start of the transactions list).
Transactions []Data `json:"transactions,omitempty"` Transactions []Data `json:"transactions,omitempty"`
// NoTxPool to disable adding any transactions from the transaction-pool. // NoTxPool to disable adding any transactions from the transaction-pool.
...@@ -317,25 +317,3 @@ type SystemConfig struct { ...@@ -317,25 +317,3 @@ type SystemConfig struct {
GasLimit uint64 `json:"gasLimit"` GasLimit uint64 `json:"gasLimit"`
// More fields can be added for future SystemConfig versions. // More fields can be added for future SystemConfig versions.
} }
// Withdrawal represents a validator withdrawal from the consensus layer.
// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#withdrawal
type Withdrawal struct {
Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer
Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal
Address common.Address `json:"address"` // target address for withdrawn ether
Amount uint64 `json:"amount"` // value of withdrawal in Gwei
}
// Withdrawals implements DerivableList for withdrawals.
type Withdrawals []Withdrawal
// Len returns the length of s.
func (s Withdrawals) Len() int { return len(s) }
// EncodeIndex encodes the i'th withdrawal to w. Note that this does not check for errors
// because we assume that *Withdrawal will only ever contain valid withdrawals that were either
// constructed by decoding or via public API in this package.
func (s Withdrawals) EncodeIndex(i int, w *bytes.Buffer) {
_ = rlp.Encode(w, s[i])
}
...@@ -5,12 +5,15 @@ import ( ...@@ -5,12 +5,15 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/ethereum-optimism/optimism/op-service/clock"
) )
// weiToEther divides the wei value by 10^18 to get a number in ether as a float64 // weiToEther divides the wei value by 10^18 to get a number in ether as a float64
...@@ -22,38 +25,27 @@ func weiToEther(wei *big.Int) float64 { ...@@ -22,38 +25,27 @@ func weiToEther(wei *big.Int) float64 {
return f return f
} }
// LaunchBalanceMetrics fires off a go rountine that queries the balance of the supplied account & periodically records it // LaunchBalanceMetrics starts a periodic query of the balance of the supplied account and records it
// to the balance metric of the namespace. The balance of the account is recorded in Ether (not Wei). // to the "balance" metric of the namespace. The balance of the account is recorded in Ether (not Wei).
// Cancel the supplied context to shut down the go routine // Cancel the supplied context to shut down the go routine
func LaunchBalanceMetrics(ctx context.Context, log log.Logger, r *prometheus.Registry, ns string, client *ethclient.Client, account common.Address) { func LaunchBalanceMetrics(log log.Logger, r *prometheus.Registry, ns string, client *ethclient.Client, account common.Address) *clock.LoopFn {
go func() { balanceGuage := promauto.With(r).NewGauge(prometheus.GaugeOpts{
balanceGuage := promauto.With(r).NewGauge(prometheus.GaugeOpts{ Namespace: ns,
Namespace: ns, Name: "balance",
Name: "balance", Help: "balance (in ether) of account " + account.String(),
Help: "balance (in ether) of account " + account.String(), })
}) return clock.NewLoopFn(clock.SystemClock, func(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
ticker := time.NewTicker(10 * time.Second) defer cancel()
defer ticker.Stop() bigBal, err := client.BalanceAt(ctx, account, nil)
if err != nil {
for { log.Warn("failed to get balance of account", "err", err, "address", account)
select { return
case <-ticker.C:
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
bigBal, err := client.BalanceAt(ctx, account, nil)
if err != nil {
log.Warn("failed to get balance of account", "err", err, "address", account)
cancel()
continue
}
bal := weiToEther(bigBal)
balanceGuage.Set(bal)
cancel()
case <-ctx.Done():
log.Info("balance metrics shutting down")
return
}
} }
bal := weiToEther(bigBal)
}() balanceGuage.Set(bal)
}, func() error {
log.Info("balance metrics shutting down")
return nil
}, 10*time.Second)
} }
...@@ -11,3 +11,7 @@ func NewRegistry() *prometheus.Registry { ...@@ -11,3 +11,7 @@ func NewRegistry() *prometheus.Registry {
registry.MustRegister(collectors.NewGoCollector()) registry.MustRegister(collectors.NewGoCollector())
return registry return registry
} }
type RegistryMetricer interface {
Registry() *prometheus.Registry
}
...@@ -125,3 +125,17 @@ func (m *RPCMetrics) RecordRPCClientResponse(method string, err error) { ...@@ -125,3 +125,17 @@ func (m *RPCMetrics) RecordRPCClientResponse(method string, err error) {
} }
m.RPCClientResponsesTotal.WithLabelValues(method, errStr).Inc() m.RPCClientResponsesTotal.WithLabelValues(method, errStr).Inc()
} }
type NoopRPCMetrics struct{}
func (n *NoopRPCMetrics) RecordRPCServerRequest(method string) func() {
return func() {}
}
func (n *NoopRPCMetrics) RecordRPCClientRequest(method string) func(err error) {
return func(err error) {}
}
func (n *NoopRPCMetrics) RecordRPCClientResponse(method string, err error) {
}
var _ RPCMetricer = (*NoopRPCMetrics)(nil)
...@@ -63,3 +63,7 @@ func (r *RollupClient) SequencerActive(ctx context.Context) (bool, error) { ...@@ -63,3 +63,7 @@ func (r *RollupClient) SequencerActive(ctx context.Context) (bool, error) {
func (r *RollupClient) SetLogLevel(ctx context.Context, lvl log.Lvl) error { func (r *RollupClient) SetLogLevel(ctx context.Context, lvl log.Lvl) error {
return r.rpc.CallContext(ctx, nil, "admin_setLogLevel", lvl.String()) return r.rpc.CallContext(ctx, nil, "admin_setLogLevel", lvl.String())
} }
func (r *RollupClient) Close() {
r.rpc.Close()
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
{"baseFeePerGas":"0x3fb7c357","difficulty":"0x0","extraData":"0x","gasLimit":"0x1c9c380","gasUsed":"0x18f759","hash":"0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452","logsBloom":"0x020010404000001a0000021000000080001100410000100001000010040200980220400000008806200200000100000000000000000000008000000400042000000050000040000112080808800002044000040004042008800480002000000000000002020020000042002400000820000080040000000010200010020010100101212050000008000000008000001010200c80000112010000438040020400000000202400000000002002a0210402000622010000000001700144000040000000002204000000c000410105024010000808000000002004002000000261000000822200200800881000000012500400400000000000000040010000800000","miner":"0x000095e79eac4d76aab57cb2c1f091d553b36ca0","mixHash":"0x5b53dc49cbab268ef9950b1d81b5e36a1b2f1b97aee1b7ff6e4db0e06c29a8b0","nonce":"0x0000000000000000","number":"0x84161e","parentHash":"0x72d92c1498e05952988d4e79a695928a6bcbd37239f8a1734051263b4d3504b8","receiptsRoot":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x2a51","stateRoot":"0xc56738518b2c7854a640ae25996d2211c9ef0dd2e4dd9e59e9d9cacef39622da","timestamp":"0x64110a5c","totalDifficulty":"0xa4a470","transactions":["0x1e8f148a9aea7d8d16ea6e9446723b8f262e8bcd89c7c961d52046ebd43b4598","0xab5c870f4c367012bd763172afbfbe68fbf35336a66ae41aff3f2c9dbf4ea3f8","0xa81fd92b2d0f0bbd3cc355f869cca3243c98c5e2641db9ecf3eeabb3b13bff6a","0xa92c7b720c08c83f1a0ed7e4c163200e30a3a8c03fcc5a51e685ea20cd0cb577","0x6921b429ad2ec1e97d3457049ad2e893b5a0349beba47ca1c74a9540af75347a","0xf776b2da0b835dde05d0d8b76fd19385d61e7055036cf637f804b36dc94f2384","0x9a08d899cd14ebb930ed59fa774afdb88a22615b3a931e930931ea54d26dc0bc","0x0fe0d97e25d5eb11a33a3e8278584c3780941fc2675bdf8fc547cee3d1fd3b17","0xef47a60f57f177a683c723c658137efab66d311e1c5abbc4d74f653535144d03","0xe23a5b35faae5335adc5aca38c5d633b00438b798c2053104b8df48406c9b141","0xd8cea4ba619b317bc05d58534af73beec6c2548b31b24d4dc61c9bbd29cfa17a","0x79a4b9d90b02c768baaad305f266281213cc75062cbe99a13222cc0c4b509498","0x6790a3bbddbeb21fcb736a59b3775755051c3a6344d8390cf8ca27f2e8a814f0","0x87ec7ace5442db252b5751ffddd38dcb04b088d36b6b0e526ff25607a4293c81","0x40cb487ecffda94f97ce7fc0f7163f2f024235df2c8291169edc80dac063e6d0","0xb76bb3d88c9b30d927c45ccfcf8d5b0054411ac8501ad588822a7d04690cccf6","0x798ebe823209869347c08bd81e04fbf60e9bdfe44b1cc923215182d0cf3d4edb","0xbe68a7e02725f799a65ebb069ccc83a014ac7c40e4119bf7c220a2f6ddfee295","0xc90c3a72efe81331727fcce4b5bd4906066da314ca9a0b44023a6b09ea7e8114","0x619a6cbd43cde074d314c19623bd66d9fb1e13c158d7138775236f798dc1245e","0xca5a56cd77b9e5b0e79020cc6346edf205bc11e901984d805125f28c2e6686e6","0x999c9ddeed67c6ef6fbf02a6e977a6c1b68e18d24814e51643c7157b87a43e0a","0x47c8f5d0b3778e4c34eba7fcc356fa04a5afd954ccf484728e72c002764dd3c4","0x396797ae0ebcdb72ff1f96fd08b6128f78acc7417353f142f1a5facd425a33e6","0x454aa43d6546a6f62246826c16b7a49c6c704238c18802ef0d659922f23a573c","0x317ecb5bd19caa42a69f836d41556ebb0e0e00e1c6cd2dee230e6e6192612527","0xc879285db5ef0a6bce98021584d16f134c1dc0aed8cc988802c4f72ba6877ff6","0xecaa2d6f597608307e5084854854ba6dc1e69395e2abea14f2c6a2fa1d6faf9a","0x4dd69b69a568ff30ae439e2ded72fbd7f2e7aaa345836703663f155c749c5eed"],"transactionsRoot":"0x4a87d0cf5990b1c5bac631583e5965c2ba943858bebb2e07f74d0b697f73821a","uncles":[],"withdrawals":[{"index":"0x1170","validatorIndex":"0x38c2c","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x66edfd65"},{"index":"0x1171","validatorIndex":"0x38c2d","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6cd228e4"},{"index":"0x1172","validatorIndex":"0x38c2e","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x77f3431b"},{"index":"0x1173","validatorIndex":"0x38c2f","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6b61f268"},{"index":"0x1174","validatorIndex":"0x38c30","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6e10bb21"},{"index":"0x1175","validatorIndex":"0x38c31","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6eb115a5"},{"index":"0x1176","validatorIndex":"0x38c32","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7caead1d"},{"index":"0x1177","validatorIndex":"0x38c33","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x772c0ddf"},{"index":"0x1178","validatorIndex":"0x38c34","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x75930a95"},{"index":"0x1179","validatorIndex":"0x38c35","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76a4db09"},{"index":"0x117a","validatorIndex":"0x38c36","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7e692b27"},{"index":"0x117b","validatorIndex":"0x38c37","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x72038ae6"},{"index":"0x117c","validatorIndex":"0x38c38","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6ccce352"},{"index":"0x117d","validatorIndex":"0x38c39","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x79ef6898"},{"index":"0x117e","validatorIndex":"0x38c3a","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6d58977d"},{"index":"0x117f","validatorIndex":"0x38c3b","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76f7d208"}],"withdrawalsRoot":"0xbe712c930a0665264b025ced87cc7839eef95a3cbc26dadc93e9e185a350ad28"}
{"baseFeePerGas":"0x3fb7c357","difficulty":"0x0","extraData":"0x","gasLimit":"0x1c9c380","gasUsed":"0x18f759","hash":"0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452","logsBloom":"0x020010404000001a0000021000000080001100410000100001000010040200980220400000008806200200000100000000000000000000008000000400042000000050000040000112080808800002044000040004042008800480002000000000000002020020000042002400000820000080040000000010200010020010100101212050000008000000008000001010200c80000112010000438040020400000000202400000000002002a0210402000622010000000001700144000040000000002204000000c000410105024010000808000000002004002000000261000000822200200800881000000012500400400000000000000040010000800000","miner":"0x000095e79eac4d76aab57cb2c1f091d553b36ca0","mixHash":"0x5b53dc49cbab268ef9950b1d81b5e36a1b2f1b97aee1b7ff6e4db0e06c29a8b0","nonce":"0x0000000000000000","number":"0x84161e","parentHash":"0x72d92c1498e05952988d4e79a695928a6bcbd37239f8a1734051263b4d3504b8","receiptsRoot":"0xaff90ae18dcc35924a4bddb68d403b8b7812c10c3ea2a114f34105c87d75bcdb","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x2a51","stateRoot":"0xc56738518b2c7854a640ae25996d2211c9ef0dd2e4dd9e59e9d9cacef39622da","timestamp":"0x64110a5c","totalDifficulty":"0xa4a470","transactions":["0x1e8f148a9aea7d8d16ea6e9446723b8f262e8bcd89c7c961d52046ebd43b4598","0xab5c870f4c367012bd763172afbfbe68fbf35336a66ae41aff3f2c9dbf4ea3f8","0xa81fd92b2d0f0bbd3cc355f869cca3243c98c5e2641db9ecf3eeabb3b13bff6a","0xa92c7b720c08c83f1a0ed7e4c163200e30a3a8c03fcc5a51e685ea20cd0cb577","0x6921b429ad2ec1e97d3457049ad2e893b5a0349beba47ca1c74a9540af75347a","0xf776b2da0b835dde05d0d8b76fd19385d61e7055036cf637f804b36dc94f2384","0x9a08d899cd14ebb930ed59fa774afdb88a22615b3a931e930931ea54d26dc0bc","0x0fe0d97e25d5eb11a33a3e8278584c3780941fc2675bdf8fc547cee3d1fd3b17","0xef47a60f57f177a683c723c658137efab66d311e1c5abbc4d74f653535144d03","0xe23a5b35faae5335adc5aca38c5d633b00438b798c2053104b8df48406c9b141","0xd8cea4ba619b317bc05d58534af73beec6c2548b31b24d4dc61c9bbd29cfa17a","0x79a4b9d90b02c768baaad305f266281213cc75062cbe99a13222cc0c4b509498","0x6790a3bbddbeb21fcb736a59b3775755051c3a6344d8390cf8ca27f2e8a814f0","0x87ec7ace5442db252b5751ffddd38dcb04b088d36b6b0e526ff25607a4293c81","0x40cb487ecffda94f97ce7fc0f7163f2f024235df2c8291169edc80dac063e6d0","0xb76bb3d88c9b30d927c45ccfcf8d5b0054411ac8501ad588822a7d04690cccf6","0x798ebe823209869347c08bd81e04fbf60e9bdfe44b1cc923215182d0cf3d4edb","0xbe68a7e02725f799a65ebb069ccc83a014ac7c40e4119bf7c220a2f6ddfee295","0xc90c3a72efe81331727fcce4b5bd4906066da314ca9a0b44023a6b09ea7e8114","0x619a6cbd43cde074d314c19623bd66d9fb1e13c158d7138775236f798dc1245e","0xca5a56cd77b9e5b0e79020cc6346edf205bc11e901984d805125f28c2e6686e6","0x999c9ddeed67c6ef6fbf02a6e977a6c1b68e18d24814e51643c7157b87a43e0a","0x47c8f5d0b3778e4c34eba7fcc356fa04a5afd954ccf484728e72c002764dd3c4","0x396797ae0ebcdb72ff1f96fd08b6128f78acc7417353f142f1a5facd425a33e6","0x454aa43d6546a6f62246826c16b7a49c6c704238c18802ef0d659922f23a573c","0x317ecb5bd19caa42a69f836d41556ebb0e0e00e1c6cd2dee230e6e6192612527","0xc879285db5ef0a6bce98021584d16f134c1dc0aed8cc988802c4f72ba6877ff6","0xecaa2d6f597608307e5084854854ba6dc1e69395e2abea14f2c6a2fa1d6faf9a","0x4dd69b69a568ff30ae439e2ded72fbd7f2e7aaa345836703663f155c749c5eed"],"transactionsRoot":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","uncles":[],"withdrawals":[{"index":"0x1170","validatorIndex":"0x38c2c","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x66edfd65"},{"index":"0x1171","validatorIndex":"0x38c2d","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6cd228e4"},{"index":"0x1172","validatorIndex":"0x38c2e","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x77f3431b"},{"index":"0x1173","validatorIndex":"0x38c2f","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6b61f268"},{"index":"0x1174","validatorIndex":"0x38c30","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6e10bb21"},{"index":"0x1175","validatorIndex":"0x38c31","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6eb115a5"},{"index":"0x1176","validatorIndex":"0x38c32","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7caead1d"},{"index":"0x1177","validatorIndex":"0x38c33","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x772c0ddf"},{"index":"0x1178","validatorIndex":"0x38c34","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x75930a95"},{"index":"0x1179","validatorIndex":"0x38c35","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76a4db09"},{"index":"0x117a","validatorIndex":"0x38c36","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7e692b27"},{"index":"0x117b","validatorIndex":"0x38c37","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x72038ae6"},{"index":"0x117c","validatorIndex":"0x38c38","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6ccce352"},{"index":"0x117d","validatorIndex":"0x38c39","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x79ef6898"},{"index":"0x117e","validatorIndex":"0x38c3a","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6d58977d"},{"index":"0x117f","validatorIndex":"0x38c3b","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76f7d208"}],"withdrawalsRoot":"0xbe712c930a0665264b025ced87cc7839eef95a3cbc26dadc93e9e185a350ad28"}
{"baseFeePerGas":"0x3fb7c357","difficulty":"0x0","extraData":"0x","gasLimit":"0x1c9c380","gasUsed":"0x18f759","hash":"0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452","logsBloom":"0x020010404000001a0000021000000080001100410000100001000010040200980220400000008806200200000100000000000000000000008000000400042000000050000040000112080808800002044000040004042008800480002000000000000002020020000042002400000820000080040000000010200010020010100101212050000008000000008000001010200c80000112010000438040020400000000202400000000002002a0210402000622010000000001700144000040000000002204000000c000410105024010000808000000002004002000000261000000822200200800881000000012500400400000000000000040010000800000","miner":"0x000095e79eac4d76aab57cb2c1f091d553b36ca0","mixHash":"0x5b53dc49cbab268ef9950b1d81b5e36a1b2f1b97aee1b7ff6e4db0e06c29a8b0","nonce":"0x0000000000000000","number":"0x84161e","parentHash":"0x72d92c1498e05952988d4e79a695928a6bcbd37239f8a1734051263b4d3504b8","receiptsRoot":"0xaff90ae18dcc35924a4bddb68d403b8b7812c10c3ea2a114f34105c87d75bcdb","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x2a51","stateRoot":"0xc56738518b2c7854a640ae25996d2211c9ef0dd2e4dd9e59e9d9cacef39622da","timestamp":"0x64110a5c","totalDifficulty":"0xa4a470","transactions":["0x1e8f148a9aea7d8d16ea6e9446723b8f262e8bcd89c7c961d52046ebd43b4598","0xab5c870f4c367012bd763172afbfbe68fbf35336a66ae41aff3f2c9dbf4ea3f8","0xa81fd92b2d0f0bbd3cc355f869cca3243c98c5e2641db9ecf3eeabb3b13bff6a","0xa92c7b720c08c83f1a0ed7e4c163200e30a3a8c03fcc5a51e685ea20cd0cb577","0x6921b429ad2ec1e97d3457049ad2e893b5a0349beba47ca1c74a9540af75347a","0xf776b2da0b835dde05d0d8b76fd19385d61e7055036cf637f804b36dc94f2384","0x9a08d899cd14ebb930ed59fa774afdb88a22615b3a931e930931ea54d26dc0bc","0x0fe0d97e25d5eb11a33a3e8278584c3780941fc2675bdf8fc547cee3d1fd3b17","0xef47a60f57f177a683c723c658137efab66d311e1c5abbc4d74f653535144d03","0xe23a5b35faae5335adc5aca38c5d633b00438b798c2053104b8df48406c9b141","0xd8cea4ba619b317bc05d58534af73beec6c2548b31b24d4dc61c9bbd29cfa17a","0x79a4b9d90b02c768baaad305f266281213cc75062cbe99a13222cc0c4b509498","0x6790a3bbddbeb21fcb736a59b3775755051c3a6344d8390cf8ca27f2e8a814f0","0x87ec7ace5442db252b5751ffddd38dcb04b088d36b6b0e526ff25607a4293c81","0x40cb487ecffda94f97ce7fc0f7163f2f024235df2c8291169edc80dac063e6d0","0xb76bb3d88c9b30d927c45ccfcf8d5b0054411ac8501ad588822a7d04690cccf6","0x798ebe823209869347c08bd81e04fbf60e9bdfe44b1cc923215182d0cf3d4edb","0xbe68a7e02725f799a65ebb069ccc83a014ac7c40e4119bf7c220a2f6ddfee295","0xc90c3a72efe81331727fcce4b5bd4906066da314ca9a0b44023a6b09ea7e8114","0x619a6cbd43cde074d314c19623bd66d9fb1e13c158d7138775236f798dc1245e","0xca5a56cd77b9e5b0e79020cc6346edf205bc11e901984d805125f28c2e6686e6","0x999c9ddeed67c6ef6fbf02a6e977a6c1b68e18d24814e51643c7157b87a43e0a","0x47c8f5d0b3778e4c34eba7fcc356fa04a5afd954ccf484728e72c002764dd3c4","0x396797ae0ebcdb72ff1f96fd08b6128f78acc7417353f142f1a5facd425a33e6","0x454aa43d6546a6f62246826c16b7a49c6c704238c18802ef0d659922f23a573c","0x317ecb5bd19caa42a69f836d41556ebb0e0e00e1c6cd2dee230e6e6192612527","0xc879285db5ef0a6bce98021584d16f134c1dc0aed8cc988802c4f72ba6877ff6","0xecaa2d6f597608307e5084854854ba6dc1e69395e2abea14f2c6a2fa1d6faf9a","0x4dd69b69a568ff30ae439e2ded72fbd7f2e7aaa345836703663f155c749c5eed"],"transactionsRoot":"0x4a87d0cf5990b1c5bac631583e5965c2ba943858bebb2e07f74d0b697f73821a","uncles":[],"withdrawals":[{"index":"0x1170","validatorIndex":"0x38c2c","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x66edfd65"},{"index":"0x1171","validatorIndex":"0x38c2d","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6cd228e4"},{"index":"0x1172","validatorIndex":"0x38c2e","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x77f3431b"},{"index":"0x1173","validatorIndex":"0x38c2f","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6b61f268"},{"index":"0x1174","validatorIndex":"0x38c30","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6e10bb21"},{"index":"0x1175","validatorIndex":"0x38c31","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6eb115a5"},{"index":"0x1176","validatorIndex":"0x38c32","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7caead1d"},{"index":"0x1177","validatorIndex":"0x38c33","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x772c0ddf"},{"index":"0x1178","validatorIndex":"0x38c34","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x75930a95"},{"index":"0x1179","validatorIndex":"0x38c35","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76a4db09"},{"index":"0x117a","validatorIndex":"0x38c36","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7e692b27"},{"index":"0x117b","validatorIndex":"0x38c37","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x72038ae6"},{"index":"0x117c","validatorIndex":"0x38c38","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6ccce352"},{"index":"0x117d","validatorIndex":"0x38c39","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x79ef6898"},{"index":"0x117e","validatorIndex":"0x38c3a","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6d58977d"},{"index":"0x117f","validatorIndex":"0x38c3b","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76f7d208"}],"withdrawalsRoot":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
{"baseFeePerGas":"0x3fb7c357","difficulty":"0x0","extraData":"0x","gasLimit":"0x1c9c380","gasUsed":"0x18f759","hash":"0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452","logsBloom":"0x020010404000001a0000021000000080001100410000100001000010040200980220400000008806200200000100000000000000000000008000000400042000000050000040000112080808800002044000040004042008800480002000000000000002020020000042002400000820000080040000000010200010020010100101212050000008000000008000001010200c80000112010000438040020400000000202400000000002002a0210402000622010000000001700144000040000000002204000000c000410105024010000808000000002004002000000261000000822200200800881000000012500400400000000000000040010000800000","miner":"0x000095e79eac4d76aab57cb2c1f091d553b36ca0","mixHash":"0x5b53dc49cbab268ef9950b1d81b5e36a1b2f1b97aee1b7ff6e4db0e06c29a8b0","nonce":"0x0000000000000000","number":"0x84161e","parentHash":"0x72d92c1498e05952988d4e79a695928a6bcbd37239f8a1734051263b4d3504b8","receiptsRoot":"0xaff90ae18dcc35924a4bddb68d403b8b7812c10c3ea2a114f34105c87d75bcdb","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x2a51","stateRoot":"0xc56738518b2c7854a640ae25996d2211c9ef0dd2e4dd9e59e9d9cacef39622da","timestamp":"0x64110a5c","totalDifficulty":"0xa4a470","transactions":["0x1e8f148a9aea7d8d16ea6e9446723b8f262e8bcd89c7c961d52046ebd43b4598","0xab5c870f4c367012bd763172afbfbe68fbf35336a66ae41aff3f2c9dbf4ea3f8","0xa81fd92b2d0f0bbd3cc355f869cca3243c98c5e2641db9ecf3eeabb3b13bff6a","0xa92c7b720c08c83f1a0ed7e4c163200e30a3a8c03fcc5a51e685ea20cd0cb577","0x6921b429ad2ec1e97d3457049ad2e893b5a0349beba47ca1c74a9540af75347a","0xf776b2da0b835dde05d0d8b76fd19385d61e7055036cf637f804b36dc94f2384","0x9a08d899cd14ebb930ed59fa774afdb88a22615b3a931e930931ea54d26dc0bc","0x0fe0d97e25d5eb11a33a3e8278584c3780941fc2675bdf8fc547cee3d1fd3b17","0xef47a60f57f177a683c723c658137efab66d311e1c5abbc4d74f653535144d03","0xe23a5b35faae5335adc5aca38c5d633b00438b798c2053104b8df48406c9b141","0xd8cea4ba619b317bc05d58534af73beec6c2548b31b24d4dc61c9bbd29cfa17a","0x79a4b9d90b02c768baaad305f266281213cc75062cbe99a13222cc0c4b509498","0x6790a3bbddbeb21fcb736a59b3775755051c3a6344d8390cf8ca27f2e8a814f0","0x87ec7ace5442db252b5751ffddd38dcb04b088d36b6b0e526ff25607a4293c81","0x40cb487ecffda94f97ce7fc0f7163f2f024235df2c8291169edc80dac063e6d0","0xb76bb3d88c9b30d927c45ccfcf8d5b0054411ac8501ad588822a7d04690cccf6","0x798ebe823209869347c08bd81e04fbf60e9bdfe44b1cc923215182d0cf3d4edb","0xbe68a7e02725f799a65ebb069ccc83a014ac7c40e4119bf7c220a2f6ddfee295","0xc90c3a72efe81331727fcce4b5bd4906066da314ca9a0b44023a6b09ea7e8114","0x619a6cbd43cde074d314c19623bd66d9fb1e13c158d7138775236f798dc1245e","0xca5a56cd77b9e5b0e79020cc6346edf205bc11e901984d805125f28c2e6686e6","0x999c9ddeed67c6ef6fbf02a6e977a6c1b68e18d24814e51643c7157b87a43e0a","0x47c8f5d0b3778e4c34eba7fcc356fa04a5afd954ccf484728e72c002764dd3c4","0x396797ae0ebcdb72ff1f96fd08b6128f78acc7417353f142f1a5facd425a33e6","0x454aa43d6546a6f62246826c16b7a49c6c704238c18802ef0d659922f23a573c","0x317ecb5bd19caa42a69f836d41556ebb0e0e00e1c6cd2dee230e6e6192612527","0xc879285db5ef0a6bce98021584d16f134c1dc0aed8cc988802c4f72ba6877ff6","0xecaa2d6f597608307e5084854854ba6dc1e69395e2abea14f2c6a2fa1d6faf9a","0x4dd69b69a568ff30ae439e2ded72fbd7f2e7aaa345836703663f155c749c5eed"],"transactionsRoot":"0x4a87d0cf5990b1c5bac631583e5965c2ba943858bebb2e07f74d0b697f73821a","uncles":[],"withdrawals":[{"index":"0x1170","validatorIndex":"0x38c2c","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x66edfd65"},{"index":"0x1171","validatorIndex":"0x38c2d","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6cd228e4"},{"index":"0x1172","validatorIndex":"0x38c2e","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x77f3431b"},{"index":"0x1173","validatorIndex":"0x38c2f","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6b61f268"},{"index":"0x1174","validatorIndex":"0x38c30","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6e10bb21"},{"index":"0x1175","validatorIndex":"0x38c31","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6eb115a5"},{"index":"0x1176","validatorIndex":"0x38c32","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7caead1d"},{"index":"0x1177","validatorIndex":"0x38c33","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x772c0ddf"},{"index":"0x1178","validatorIndex":"0x38c34","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x75930a95"},{"index":"0x1179","validatorIndex":"0x38c35","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76a4db09"},{"index":"0x117a","validatorIndex":"0x38c36","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x7e692b27"},{"index":"0x117b","validatorIndex":"0x38c37","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x72038ae6"},{"index":"0x117c","validatorIndex":"0x38c38","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6ccce352"},{"index":"0x117d","validatorIndex":"0x38c39","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x79ef6898"},{"index":"0x117e","validatorIndex":"0x38c3a","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x6d58977d"},{"index":"0x117f","validatorIndex":"0x38c3b","address":"0x8f0844fd51e31ff6bf5babe21dccf7328e19fd9f","amount":"0x76f7d208"}],"withdrawalsRoot":"0xbe712c930a0665264b025ced87cc7839eef95a3cbc26dadc93e9e185a350ad28"}
{"baseFeePerGas":"0x7ccf990f8","difficulty":"0x0","extraData":"0xd883010b02846765746888676f312e32302e31856c696e7578","gasLimit":"0x1c9c380","gasUsed":"0xa79638","hash":"0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663","logsBloom":"0xb034000008010014411408c080a0018440087220211154100005a1388807241142a2504080034a00111212a47f05008520200000280202a12800538cc06488486a0141989c7800c0c848011f02249661800e08449145b040a252d18082c009000641004052c80102000804ac10901c24032000980010438a01e50a90a0d8008c138c21204040000b20425000833041028000148124c2012d0aa8d1d0548301808228002015184090000224021040d68220100210220480420308455c382a40020130dc42502986080600000115034c0401c81828490410308005610048026b822e10b4228071ba00bdd20140621b2000c02012300808084181ac308200000011","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x31f0c0305fc07a93b1a33da339c79aadbe8d9811c78d2b514cd18d64e1328f25","nonce":"0x0000000000000000","number":"0x840249","parentHash":"0x2303b55af4add799b19275a491b150c1a03075395f87a7856a4e3327595ed7df","receiptsRoot":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0xea6d","stateRoot":"0xd12bf4cf3941cf48be329a939b13d3403d326841c69cdcc9a9c13ab2f227e904","timestamp":"0x640fdeb0","totalDifficulty":"0xa4a470","transactions":["0x39c666d9b5cec429accad7b0f94f789ca2ebeb5294b8b129c1b76f552daf57d3","0x2ca7289ab3738d17e0f5093bd96c97c06c9a2ea4c22fc84a6a7fbfda93ce55ee","0xb0085de1476530de3efc6928c4683e7c40f8fac18875f74cbcc47df159de17d9","0xe01c8631c86ded63af95b8dbc0c8aac5d31254c14d6ecb4cc51d98259d838e52","0x69414a126a6f07ab5e31ad2f9069fb986b7c490e096898473873e41ece6af783","0xa2fef1133ee726533c7f190f246fede123e3706a03933c1febc92618f90d2804","0x6585ec5c4c2bbf1f683f90f58e18f3b38d875e94457fe4cbb7bc5bf6581f83af","0x1db276b864fbf01dcf8cededf8d597553ecb0eb9438edfaf2f5bd0cc93297c66","0xcbe7ed31654af4e191ca53445b82de040ae2cd92459a3f951bdcce423d780f08","0x808ba5211f03cc78a732ff0f9383c6355e63c83ae8c6035ced2ba6f7c331dc63","0xdd66f1f26672849ef54c420210f479c9f0c46924d8e9f7b210981ffe8d3fac82","0x254abb2f8cdcffe9ef62ab924312a1e4142578db87e4f7c199fd35991e92f014","0xa7b7c654e7073b8043b680b7ffc95d3f2099abaa0b0578d6f954a2a7c99404e1","0x7ccdfa698c8acf47ab9316ed078eb40819ff575bcf612c6f59f29e7726df3f96","0xa0b035ef315824a6f6a6565fa8de27042ade3af9cf0583a36dea83d6e01bf2a8","0x1ebad7f3e8cb3543d4963686a94d99f61839f666831eab9c9c1b4711de11d3d9","0x501750278e91d8b5be1ccf60e793d4bbcd9b3bb3ccc518d3634a71caeac65f48","0xd80ff8af29ae163d5811ba511e60b3a87a279f677bb3872a0f1aa6d0a226e880","0x096acab3b3fe47b149d375782d1eb00b9fef7904076d60c54b3c197b04e6bf82","0xbe9d1738af74a22400591a9a808fb01a25ab41e2e56f202dd7251eb113e8ceeb","0x0834c720e55cccd97aaf4f8fb0cb66afb9881fb6a762c0f70473ec53f98a712e","0x51a0c33c9b37245b416575bdd2751c0d8a5d8bead49585ac427bfc873d4016af","0x531c25d51ccda59aa9ea82e85c99be9dd4e285af9b8973cbab9ac4a38e26e55a","0x93ac6c08d21cb1b61ff59e5e2d6fa3f9ad54008b0a66c669199050bef219f6e3","0x3792db6dd6285f409e4281951e9f78dad16c4a78072ff1c909dfadea5658d857","0xd2d51764c01e8c0a43fbe362704388df5bacf7e5e620c3864e242530ffb3e828","0x516b0227d9e64eb6e0de6862764d40f5376b5f12fec878436fea3479b4c36bb8","0x81b0abc78b82840adb666775b182a9e292f663b64bcd35004c04436ed3c8281c","0xd0287570d431d2baea96ecc81cb890e7f4f06ab5df02f9b4067768abca19acb5","0x76ddab2674369f34946c5fa2f05e2aa8566d86235b83e808e9b27bc106e04ac7","0x34a5c74011a2c8a00103bc91bfbfd94aa99cd569be69066e4bf64d188fe8714e","0x7b9730ead1b9f59b206d0ddea87be9383ba3fc7b496c7863b0cb847889b86617","0x77166ee0409ba86bd26e7c03ad1a927abaf5af8a8a37149e725cd37512091dd6","0x3c2b6c2ae505c5c36d5f316c1fcb5f54f7346ed35ae35c93462991ded7968a68","0xf99a792837e13827b5e0a8915fb59c760babc95d242feca99a5594e64ff6b6e2","0x522313f5d923f048ae5bd0b5595c1f4fc883bc0b3cf3cb0939d3fcf8b08c829c","0x471ceb0e85af594aa56deca54cb8198567b2afd8406722ea530077aaa6b641b3","0x3e9dca502e9039ae0c6d642f62e9562ff00010c6bfbb8234a6135712ba70dfda","0xc95cac67267f4accb9b5950316ac64772f7d082bed6b712c09cf2da0bdc237b7","0xfca28fdbd13fc16daf7aec7d4a2ad2c6b5f0b2a7b0fb1d9167c09b5e115ff26e","0xc73124ca798b2f7a5df2ea4d568efab2f41b135130ea5cc41d4bcb4b5c57d5bd","0x29abb76b5e7a5ce137bf9c22474d386eb58d249f43178d2b2e15c16dfdc5ca80","0x03e5ab25a58bd44fb9dd0c698b323eab8b8363479dfcbcbb16d0a0bd983880ae","0x3c8ee80ddea7fa2d2b75e44563c10c10756f598e8ad252a49c5d3e8a5c8e6cbf","0xaffa73b68bc7ab0c3f5e28377f5ca0a5df33c0a485f64dc094b7f6ae23353203","0xc66c9c66fbc8fe97fcc16506cde7a58689af1004a18c6171cfe763bcd94f50b2","0x80fec96707519172b53790610d5800cd09a4243aca9bacfa956c56337d06f820","0x61b33bfcf11214906dcdce7d7ed83ad82f38184c03ded07f7782059d02eeedea","0x5d4138d4e28a8327e506cb012346b1b38b65f615a2b991d35cf5d4de244b3e6d","0x875a142b6dfcf10ffb71a7afe0ce4672c047fc7e162ba0383390516d6334d45d","0x79b6df832bfbd04085d0b005a6e3ad8f00fc8717eed59280aa8107268b71e7e0","0xcb2fb25d268f65dc9312e89bd3c328c9847a3c9da282026793c54a745f825ab5","0xe483d4a36ad19fd5eacb7f6d9ad3ce080ad70ac673273e710f6e3d5acbc6559c","0x0564242c37d5013b671ef4864394cc0f3924c589f8aad64118223a9af2f164f6","0x48db358e80b278c3a46c2a166339797060a40f33984a5d974992cd9722139d5d","0x69d7758db91fae31fa35ecbed4d40897c5087f45dc796cd796b8ceead21f972e","0x2951478916ecd27a8e808d08f85be4bf2c0b0e0546f21f4e309145dd96eb8df1","0xaca9028cb5d55bbf71b7bff9884a9a3b0b38a575ffc8f8807ce345cf8bd298ef","0xc7f625a19ee41a1750eac9428b4394a9a2476b8ea2d31b4c2f9f5b4fcb86cae3","0x45499074aa521ac4151138f0aad969bcc2dfc1648d22ff8c42e51c74cb77414d","0x00b5b05c6d1a2eb8abe2c383da600516515e383fc8a29953bb6e6d167e9705b2","0x6fc411f24c7b4b8d821b45de32b9edc5ac998d1ac748a98abe8e983c6f39fc19"],"transactionsRoot":"0x1ad3212eca045505cfc4cacf675b5fa2e7dc7b9f9cee88191464f97d1c9fbca4","uncles":[]}
{"baseFeePerGas":"0x7ccf990f8","difficulty":"0x0","extraData":"0xd883010b02846765746888676f312e32302e31856c696e7578","gasLimit":"0x1c9c380","gasUsed":"0xa79638","hash":"0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663","logsBloom":"0xb034000008010014411408c080a0018440087220211154100005a1388807241142a2504080034a00111212a47f05008520200000280202a12800538cc06488486a0141989c7800c0c848011f02249661800e08449145b040a252d18082c009000641004052c80102000804ac10901c24032000980010438a01e50a90a0d8008c138c21204040000b20425000833041028000148124c2012d0aa8d1d0548301808228002015184090000224021040d68220100210220480420308455c382a40020130dc42502986080600000115034c0401c81828490410308005610048026b822e10b4228071ba00bdd20140621b2000c02012300808084181ac308200000011","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x31f0c0305fc07a93b1a33da339c79aadbe8d9811c78d2b514cd18d64e1328f25","nonce":"0x0000000000000000","number":"0x840249","parentHash":"0x2303b55af4add799b19275a491b150c1a03075395f87a7856a4e3327595ed7df","receiptsRoot":"0x99da71b17ae1929db912c3315ebe349d37f2bb600454616fdde0ee90d6dbc59e","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0xea6d","stateRoot":"0xd12bf4cf3941cf48be329a939b13d3403d326841c69cdcc9a9c13ab2f227e904","timestamp":"0x640fdeb0","totalDifficulty":"0xa4a470","transactions":["0x39c666d9b5cec429accad7b0f94f789ca2ebeb5294b8b129c1b76f552daf57d3","0x2ca7289ab3738d17e0f5093bd96c97c06c9a2ea4c22fc84a6a7fbfda93ce55ee","0xb0085de1476530de3efc6928c4683e7c40f8fac18875f74cbcc47df159de17d9","0xe01c8631c86ded63af95b8dbc0c8aac5d31254c14d6ecb4cc51d98259d838e52","0x69414a126a6f07ab5e31ad2f9069fb986b7c490e096898473873e41ece6af783","0xa2fef1133ee726533c7f190f246fede123e3706a03933c1febc92618f90d2804","0x6585ec5c4c2bbf1f683f90f58e18f3b38d875e94457fe4cbb7bc5bf6581f83af","0x1db276b864fbf01dcf8cededf8d597553ecb0eb9438edfaf2f5bd0cc93297c66","0xcbe7ed31654af4e191ca53445b82de040ae2cd92459a3f951bdcce423d780f08","0x808ba5211f03cc78a732ff0f9383c6355e63c83ae8c6035ced2ba6f7c331dc63","0xdd66f1f26672849ef54c420210f479c9f0c46924d8e9f7b210981ffe8d3fac82","0x254abb2f8cdcffe9ef62ab924312a1e4142578db87e4f7c199fd35991e92f014","0xa7b7c654e7073b8043b680b7ffc95d3f2099abaa0b0578d6f954a2a7c99404e1","0x7ccdfa698c8acf47ab9316ed078eb40819ff575bcf612c6f59f29e7726df3f96","0xa0b035ef315824a6f6a6565fa8de27042ade3af9cf0583a36dea83d6e01bf2a8","0x1ebad7f3e8cb3543d4963686a94d99f61839f666831eab9c9c1b4711de11d3d9","0x501750278e91d8b5be1ccf60e793d4bbcd9b3bb3ccc518d3634a71caeac65f48","0xd80ff8af29ae163d5811ba511e60b3a87a279f677bb3872a0f1aa6d0a226e880","0x096acab3b3fe47b149d375782d1eb00b9fef7904076d60c54b3c197b04e6bf82","0xbe9d1738af74a22400591a9a808fb01a25ab41e2e56f202dd7251eb113e8ceeb","0x0834c720e55cccd97aaf4f8fb0cb66afb9881fb6a762c0f70473ec53f98a712e","0x51a0c33c9b37245b416575bdd2751c0d8a5d8bead49585ac427bfc873d4016af","0x531c25d51ccda59aa9ea82e85c99be9dd4e285af9b8973cbab9ac4a38e26e55a","0x93ac6c08d21cb1b61ff59e5e2d6fa3f9ad54008b0a66c669199050bef219f6e3","0x3792db6dd6285f409e4281951e9f78dad16c4a78072ff1c909dfadea5658d857","0xd2d51764c01e8c0a43fbe362704388df5bacf7e5e620c3864e242530ffb3e828","0x516b0227d9e64eb6e0de6862764d40f5376b5f12fec878436fea3479b4c36bb8","0x81b0abc78b82840adb666775b182a9e292f663b64bcd35004c04436ed3c8281c","0xd0287570d431d2baea96ecc81cb890e7f4f06ab5df02f9b4067768abca19acb5","0x76ddab2674369f34946c5fa2f05e2aa8566d86235b83e808e9b27bc106e04ac7","0x34a5c74011a2c8a00103bc91bfbfd94aa99cd569be69066e4bf64d188fe8714e","0x7b9730ead1b9f59b206d0ddea87be9383ba3fc7b496c7863b0cb847889b86617","0x77166ee0409ba86bd26e7c03ad1a927abaf5af8a8a37149e725cd37512091dd6","0x3c2b6c2ae505c5c36d5f316c1fcb5f54f7346ed35ae35c93462991ded7968a68","0xf99a792837e13827b5e0a8915fb59c760babc95d242feca99a5594e64ff6b6e2","0x522313f5d923f048ae5bd0b5595c1f4fc883bc0b3cf3cb0939d3fcf8b08c829c","0x471ceb0e85af594aa56deca54cb8198567b2afd8406722ea530077aaa6b641b3","0x3e9dca502e9039ae0c6d642f62e9562ff00010c6bfbb8234a6135712ba70dfda","0xc95cac67267f4accb9b5950316ac64772f7d082bed6b712c09cf2da0bdc237b7","0xfca28fdbd13fc16daf7aec7d4a2ad2c6b5f0b2a7b0fb1d9167c09b5e115ff26e","0xc73124ca798b2f7a5df2ea4d568efab2f41b135130ea5cc41d4bcb4b5c57d5bd","0x29abb76b5e7a5ce137bf9c22474d386eb58d249f43178d2b2e15c16dfdc5ca80","0x03e5ab25a58bd44fb9dd0c698b323eab8b8363479dfcbcbb16d0a0bd983880ae","0x3c8ee80ddea7fa2d2b75e44563c10c10756f598e8ad252a49c5d3e8a5c8e6cbf","0xaffa73b68bc7ab0c3f5e28377f5ca0a5df33c0a485f64dc094b7f6ae23353203","0xc66c9c66fbc8fe97fcc16506cde7a58689af1004a18c6171cfe763bcd94f50b2","0x80fec96707519172b53790610d5800cd09a4243aca9bacfa956c56337d06f820","0x61b33bfcf11214906dcdce7d7ed83ad82f38184c03ded07f7782059d02eeedea","0x5d4138d4e28a8327e506cb012346b1b38b65f615a2b991d35cf5d4de244b3e6d","0x875a142b6dfcf10ffb71a7afe0ce4672c047fc7e162ba0383390516d6334d45d","0x79b6df832bfbd04085d0b005a6e3ad8f00fc8717eed59280aa8107268b71e7e0","0xcb2fb25d268f65dc9312e89bd3c328c9847a3c9da282026793c54a745f825ab5","0xe483d4a36ad19fd5eacb7f6d9ad3ce080ad70ac673273e710f6e3d5acbc6559c","0x0564242c37d5013b671ef4864394cc0f3924c589f8aad64118223a9af2f164f6","0x48db358e80b278c3a46c2a166339797060a40f33984a5d974992cd9722139d5d","0x69d7758db91fae31fa35ecbed4d40897c5087f45dc796cd796b8ceead21f972e","0x2951478916ecd27a8e808d08f85be4bf2c0b0e0546f21f4e309145dd96eb8df1","0xaca9028cb5d55bbf71b7bff9884a9a3b0b38a575ffc8f8807ce345cf8bd298ef","0xc7f625a19ee41a1750eac9428b4394a9a2476b8ea2d31b4c2f9f5b4fcb86cae3","0x45499074aa521ac4151138f0aad969bcc2dfc1648d22ff8c42e51c74cb77414d","0x00b5b05c6d1a2eb8abe2c383da600516515e383fc8a29953bb6e6d167e9705b2","0x6fc411f24c7b4b8d821b45de32b9edc5ac998d1ac748a98abe8e983c6f39fc19"],"transactionsRoot":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","uncles":[]}
{"baseFeePerGas":"0x7ccf990f8","difficulty":"0x0","extraData":"0xd883010b02846765746888676f312e32302e31856c696e7578","gasLimit":"0x1c9c380","gasUsed":"0xa79638","hash":"0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663","logsBloom":"0xb034000008010014411408c080a0018440087220211154100005a1388807241142a2504080034a00111212a47f05008520200000280202a12800538cc06488486a0141989c7800c0c848011f02249661800e08449145b040a252d18082c009000641004052c80102000804ac10901c24032000980010438a01e50a90a0d8008c138c21204040000b20425000833041028000148124c2012d0aa8d1d0548301808228002015184090000224021040d68220100210220480420308455c382a40020130dc42502986080600000115034c0401c81828490410308005610048026b822e10b4228071ba00bdd20140621b2000c02012300808084181ac308200000011","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x31f0c0305fc07a93b1a33da339c79aadbe8d9811c78d2b514cd18d64e1328f25","nonce":"0x0000000000000000","number":"0x840249","parentHash":"0x2303b55af4add799b19275a491b150c1a03075395f87a7856a4e3327595ed7df","receiptsRoot":"0x99da71b17ae1929db912c3315ebe349d37f2bb600454616fdde0ee90d6dbc59e","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0xea6d","stateRoot":"0xd12bf4cf3941cf48be329a939b13d3403d326841c69cdcc9a9c13ab2f227e904","timestamp":"0x640fdeb0","totalDifficulty":"0xa4a470","transactions":["0x39c666d9b5cec429accad7b0f94f789ca2ebeb5294b8b129c1b76f552daf57d3","0x2ca7289ab3738d17e0f5093bd96c97c06c9a2ea4c22fc84a6a7fbfda93ce55ee","0xb0085de1476530de3efc6928c4683e7c40f8fac18875f74cbcc47df159de17d9","0xe01c8631c86ded63af95b8dbc0c8aac5d31254c14d6ecb4cc51d98259d838e52","0x69414a126a6f07ab5e31ad2f9069fb986b7c490e096898473873e41ece6af783","0xa2fef1133ee726533c7f190f246fede123e3706a03933c1febc92618f90d2804","0x6585ec5c4c2bbf1f683f90f58e18f3b38d875e94457fe4cbb7bc5bf6581f83af","0x1db276b864fbf01dcf8cededf8d597553ecb0eb9438edfaf2f5bd0cc93297c66","0xcbe7ed31654af4e191ca53445b82de040ae2cd92459a3f951bdcce423d780f08","0x808ba5211f03cc78a732ff0f9383c6355e63c83ae8c6035ced2ba6f7c331dc63","0xdd66f1f26672849ef54c420210f479c9f0c46924d8e9f7b210981ffe8d3fac82","0x254abb2f8cdcffe9ef62ab924312a1e4142578db87e4f7c199fd35991e92f014","0xa7b7c654e7073b8043b680b7ffc95d3f2099abaa0b0578d6f954a2a7c99404e1","0x7ccdfa698c8acf47ab9316ed078eb40819ff575bcf612c6f59f29e7726df3f96","0xa0b035ef315824a6f6a6565fa8de27042ade3af9cf0583a36dea83d6e01bf2a8","0x1ebad7f3e8cb3543d4963686a94d99f61839f666831eab9c9c1b4711de11d3d9","0x501750278e91d8b5be1ccf60e793d4bbcd9b3bb3ccc518d3634a71caeac65f48","0xd80ff8af29ae163d5811ba511e60b3a87a279f677bb3872a0f1aa6d0a226e880","0x096acab3b3fe47b149d375782d1eb00b9fef7904076d60c54b3c197b04e6bf82","0xbe9d1738af74a22400591a9a808fb01a25ab41e2e56f202dd7251eb113e8ceeb","0x0834c720e55cccd97aaf4f8fb0cb66afb9881fb6a762c0f70473ec53f98a712e","0x51a0c33c9b37245b416575bdd2751c0d8a5d8bead49585ac427bfc873d4016af","0x531c25d51ccda59aa9ea82e85c99be9dd4e285af9b8973cbab9ac4a38e26e55a","0x93ac6c08d21cb1b61ff59e5e2d6fa3f9ad54008b0a66c669199050bef219f6e3","0x3792db6dd6285f409e4281951e9f78dad16c4a78072ff1c909dfadea5658d857","0xd2d51764c01e8c0a43fbe362704388df5bacf7e5e620c3864e242530ffb3e828","0x516b0227d9e64eb6e0de6862764d40f5376b5f12fec878436fea3479b4c36bb8","0x81b0abc78b82840adb666775b182a9e292f663b64bcd35004c04436ed3c8281c","0xd0287570d431d2baea96ecc81cb890e7f4f06ab5df02f9b4067768abca19acb5","0x76ddab2674369f34946c5fa2f05e2aa8566d86235b83e808e9b27bc106e04ac7","0x34a5c74011a2c8a00103bc91bfbfd94aa99cd569be69066e4bf64d188fe8714e","0x7b9730ead1b9f59b206d0ddea87be9383ba3fc7b496c7863b0cb847889b86617","0x77166ee0409ba86bd26e7c03ad1a927abaf5af8a8a37149e725cd37512091dd6","0x3c2b6c2ae505c5c36d5f316c1fcb5f54f7346ed35ae35c93462991ded7968a68","0xf99a792837e13827b5e0a8915fb59c760babc95d242feca99a5594e64ff6b6e2","0x522313f5d923f048ae5bd0b5595c1f4fc883bc0b3cf3cb0939d3fcf8b08c829c","0x471ceb0e85af594aa56deca54cb8198567b2afd8406722ea530077aaa6b641b3","0x3e9dca502e9039ae0c6d642f62e9562ff00010c6bfbb8234a6135712ba70dfda","0xc95cac67267f4accb9b5950316ac64772f7d082bed6b712c09cf2da0bdc237b7","0xfca28fdbd13fc16daf7aec7d4a2ad2c6b5f0b2a7b0fb1d9167c09b5e115ff26e","0xc73124ca798b2f7a5df2ea4d568efab2f41b135130ea5cc41d4bcb4b5c57d5bd","0x29abb76b5e7a5ce137bf9c22474d386eb58d249f43178d2b2e15c16dfdc5ca80","0x03e5ab25a58bd44fb9dd0c698b323eab8b8363479dfcbcbb16d0a0bd983880ae","0x3c8ee80ddea7fa2d2b75e44563c10c10756f598e8ad252a49c5d3e8a5c8e6cbf","0xaffa73b68bc7ab0c3f5e28377f5ca0a5df33c0a485f64dc094b7f6ae23353203","0xc66c9c66fbc8fe97fcc16506cde7a58689af1004a18c6171cfe763bcd94f50b2","0x80fec96707519172b53790610d5800cd09a4243aca9bacfa956c56337d06f820","0x61b33bfcf11214906dcdce7d7ed83ad82f38184c03ded07f7782059d02eeedea","0x5d4138d4e28a8327e506cb012346b1b38b65f615a2b991d35cf5d4de244b3e6d","0x875a142b6dfcf10ffb71a7afe0ce4672c047fc7e162ba0383390516d6334d45d","0x79b6df832bfbd04085d0b005a6e3ad8f00fc8717eed59280aa8107268b71e7e0","0xcb2fb25d268f65dc9312e89bd3c328c9847a3c9da282026793c54a745f825ab5","0xe483d4a36ad19fd5eacb7f6d9ad3ce080ad70ac673273e710f6e3d5acbc6559c","0x0564242c37d5013b671ef4864394cc0f3924c589f8aad64118223a9af2f164f6","0x48db358e80b278c3a46c2a166339797060a40f33984a5d974992cd9722139d5d","0x69d7758db91fae31fa35ecbed4d40897c5087f45dc796cd796b8ceead21f972e","0x2951478916ecd27a8e808d08f85be4bf2c0b0e0546f21f4e309145dd96eb8df1","0xaca9028cb5d55bbf71b7bff9884a9a3b0b38a575ffc8f8807ce345cf8bd298ef","0xc7f625a19ee41a1750eac9428b4394a9a2476b8ea2d31b4c2f9f5b4fcb86cae3","0x45499074aa521ac4151138f0aad969bcc2dfc1648d22ff8c42e51c74cb77414d","0x00b5b05c6d1a2eb8abe2c383da600516515e383fc8a29953bb6e6d167e9705b2","0x6fc411f24c7b4b8d821b45de32b9edc5ac998d1ac748a98abe8e983c6f39fc19"],"transactionsRoot":"0x1ad3212eca045505cfc4cacf675b5fa2e7dc7b9f9cee88191464f97d1c9fbca4","uncles":[]}
#!/bin/bash
set -euo pipefail
SOURCE_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$SOURCE_DIR"
export ETH_RPC_URL=https://ethereum-goerli-rpc.allthatnode.com
jq_mutate() {
local name="$1"
jq -c "$2" "$name" > "$name.tmp" && mv "$name.tmp" "$name"
}
success_case() {
# just format the files
jq_mutate "$1" .
jq_mutate "$2" .
}
bad_receipts_root() {
local data_file="$1"
local metadata_file="$2"
jq_mutate "$data_file" '. + {"receiptsRoot": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}'
jq_mutate "$metadata_file" '. + {"fail": true}'
}
bad_withdrawals_root() {
local data_file="$1"
local metadata_file="$2"
jq_mutate "$data_file" '. + {"withdrawalsRoot": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}'
jq_mutate "$metadata_file" '. + {"fail": true}'
}
bad_transactions_root() {
local data_file="$1"
local metadata_file="$2"
jq_mutate "$data_file" '. + {"transactionsRoot": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}'
jq_mutate "$metadata_file" '. + {"fail": true}'
}
generate_test_vector() {
local name="$1"
local blockhash="$2"
local fulltxs="$3"
local mutation_func="$4"
local metadata_file=""
local data_file=""
if [[ "$fulltxs" == true ]]; then
metadata_file="data/blocks/${name}_metadata.json"
data_file="data/blocks/${name}_data.json"
else
metadata_file="data/headers/${name}_metadata.json"
data_file="data/headers/${name}_data.json"
fi
echo "{\"name\": \"$name\"}" > "$metadata_file"
cast rpc eth_getBlockByHash $blockhash $fulltxs > $data_file
# Mutate data using the provided function
$mutation_func "$data_file" $metadata_file
}
mkdir -p data/blocks
mkdir -p data/headers
# Blocks
generate_test_vector "pre-shanghai-success" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" true success_case
generate_test_vector "pre-shanghai-bad-transactions" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" true bad_transactions_root
generate_test_vector "pre-shanghai-bad-receipts" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" true bad_receipts_root
generate_test_vector "post-shanghai-success" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true success_case
generate_test_vector "post-shanghai-bad-withdrawals" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true bad_withdrawals_root
generate_test_vector "post-shanghai-bad-transactions" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true bad_transactions_root
generate_test_vector "post-shanghai-bad-receipts" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true bad_receipts_root
# Headers
generate_test_vector "pre-shanghai-success" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" false success_case
generate_test_vector "pre-shanghai-bad-transactions" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" false bad_transactions_root
generate_test_vector "pre-shanghai-bad-receipts" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" false bad_receipts_root
generate_test_vector "post-shanghai-success" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" false success_case
generate_test_vector "post-shanghai-bad-withdrawals" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" false bad_withdrawals_root
generate_test_vector "post-shanghai-bad-transactions" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" false bad_transactions_root
generate_test_vector "post-shanghai-bad-receipts" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" false bad_receipts_root
...@@ -181,16 +181,38 @@ func (hdr *rpcHeader) Info(trustCache bool, mustBePostMerge bool) (eth.BlockInfo ...@@ -181,16 +181,38 @@ func (hdr *rpcHeader) Info(trustCache bool, mustBePostMerge bool) (eth.BlockInfo
type rpcBlock struct { type rpcBlock struct {
rpcHeader rpcHeader
Transactions []*types.Transaction `json:"transactions"` Transactions []*types.Transaction `json:"transactions"`
Withdrawals *eth.Withdrawals `json:"withdrawals,omitempty"` Withdrawals *types.Withdrawals `json:"withdrawals,omitempty"`
} }
func (block *rpcBlock) verify() error { func (block *rpcBlock) verify() error {
if computed := block.computeBlockHash(); computed != block.Hash { if computed := block.computeBlockHash(); computed != block.Hash {
return fmt.Errorf("failed to verify block hash: computed %s but RPC said %s", computed, block.Hash) return fmt.Errorf("failed to verify block hash: computed %s but RPC said %s", computed, block.Hash)
} }
for i, tx := range block.Transactions {
if tx == nil {
return fmt.Errorf("block tx %d is null", i)
}
}
if computed := types.DeriveSha(types.Transactions(block.Transactions), trie.NewStackTrie(nil)); block.TxHash != computed { if computed := types.DeriveSha(types.Transactions(block.Transactions), trie.NewStackTrie(nil)); block.TxHash != computed {
return fmt.Errorf("failed to verify transactions list: computed %s but RPC said %s", computed, block.TxHash) return fmt.Errorf("failed to verify transactions list: computed %s but RPC said %s", computed, block.TxHash)
} }
if block.WithdrawalsRoot != nil {
if block.Withdrawals == nil {
return fmt.Errorf("expected withdrawals")
}
for i, w := range *block.Withdrawals {
if w == nil {
return fmt.Errorf("block withdrawal %d is null", i)
}
}
if computed := types.DeriveSha(*block.Withdrawals, trie.NewStackTrie(nil)); *block.WithdrawalsRoot != computed {
return fmt.Errorf("failed to verify withdrawals list: computed %s but RPC said %s", computed, block.WithdrawalsRoot)
}
} else {
if block.Withdrawals != nil {
return fmt.Errorf("expected no withdrawals due to missing withdrawals-root, but got %d", len(*block.Withdrawals))
}
}
return nil return nil
} }
......
This diff is collapsed.
...@@ -238,23 +238,15 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (* ...@@ -238,23 +238,15 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
rawTx.Gas = gas rawTx.Gas = gas
} }
// Avoid bumping the nonce if the gas estimation fails. return m.signWithNextNonce(ctx, rawTx)
nonce, err := m.nextNonce(ctx)
if err != nil {
return nil, err
}
rawTx.Nonce = nonce
ctx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
return m.cfg.Signer(ctx, m.cfg.From, types.NewTx(rawTx))
} }
// nextNonce returns a nonce to use for the next transaction. It uses // signWithNextNonce returns a signed transaction with the next available nonce.
// eth_getTransactionCount with "latest" once, and then subsequent calls simply // The nonce is fetched once using eth_getTransactionCount with "latest", and
// increment this number. If the transaction manager is reset, it will query the // then subsequent calls simply increment this number. If the transaction manager
// eth_getTransactionCount nonce again. // is reset, it will query the eth_getTransactionCount nonce again. If signing
func (m *SimpleTxManager) nextNonce(ctx context.Context) (uint64, error) { // fails, the nonce is not incremented.
func (m *SimpleTxManager) signWithNextNonce(ctx context.Context, rawTx *types.DynamicFeeTx) (*types.Transaction, error) {
m.nonceLock.Lock() m.nonceLock.Lock()
defer m.nonceLock.Unlock() defer m.nonceLock.Unlock()
...@@ -265,15 +257,25 @@ func (m *SimpleTxManager) nextNonce(ctx context.Context) (uint64, error) { ...@@ -265,15 +257,25 @@ func (m *SimpleTxManager) nextNonce(ctx context.Context) (uint64, error) {
nonce, err := m.backend.NonceAt(childCtx, m.cfg.From, nil) nonce, err := m.backend.NonceAt(childCtx, m.cfg.From, nil)
if err != nil { if err != nil {
m.metr.RPCError() m.metr.RPCError()
return 0, fmt.Errorf("failed to get nonce: %w", err) return nil, fmt.Errorf("failed to get nonce: %w", err)
} }
m.nonce = &nonce m.nonce = &nonce
} else { } else {
*m.nonce++ *m.nonce++
} }
m.metr.RecordNonce(*m.nonce) rawTx.Nonce = *m.nonce
return *m.nonce, nil ctx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
tx, err := m.cfg.Signer(ctx, m.cfg.From, types.NewTx(rawTx))
if err != nil {
// decrement the nonce, so we can retry signing with the same nonce next time
// signWithNextNonce is called
*m.nonce--
} else {
m.metr.RecordNonce(*m.nonce)
}
return tx, err
} }
// resetNonce resets the internal nonce tracking. This is called if any pending send // resetNonce resets the internal nonce tracking. This is called if any pending send
......
...@@ -450,6 +450,40 @@ func TestTxMgr_EstimateGasFails(t *testing.T) { ...@@ -450,6 +450,40 @@ func TestTxMgr_EstimateGasFails(t *testing.T) {
require.Equal(t, lastNonce+1, tx.Nonce()) require.Equal(t, lastNonce+1, tx.Nonce())
} }
func TestTxMgr_SigningFails(t *testing.T) {
t.Parallel()
errorSigning := false
cfg := configWithNumConfs(1)
cfg.Signer = func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
if errorSigning {
return nil, fmt.Errorf("signer error")
} else {
return tx, nil
}
}
h := newTestHarnessWithConfig(t, cfg)
candidate := h.createTxCandidate()
// Set the gas limit to zero to trigger gas estimation.
candidate.GasLimit = 0
// Craft a successful transaction.
tx, err := h.mgr.craftTx(context.Background(), candidate)
require.Nil(t, err)
lastNonce := tx.Nonce()
// Mock signer failure.
errorSigning = true
_, err = h.mgr.craftTx(context.Background(), candidate)
require.ErrorContains(t, err, "signer error")
// Ensure successful craft uses the correct nonce
errorSigning = false
tx, err = h.mgr.craftTx(context.Background(), candidate)
require.Nil(t, err)
require.Equal(t, lastNonce+1, tx.Nonce())
}
// TestTxMgrOnlyOnePublicationSucceeds asserts that the tx manager will return a // TestTxMgrOnlyOnePublicationSucceeds asserts that the tx manager will return a
// receipt so long as at least one of the publications is able to succeed with a // receipt so long as at least one of the publications is able to succeed with a
// simulated rpc failure. // simulated rpc failure.
......
package op_service
func FormatVersion(version string, gitCommit string, gitDate string, meta string) string {
v := version
if gitCommit != "" {
if len(gitCommit) >= 8 {
v += "-" + gitCommit[:8]
} else {
v += "-" + gitCommit
}
}
if gitDate != "" {
v += "-" + gitDate
}
if meta != "" {
v += "-" + meta
}
return v
}
package op_service
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestFormatVersion(t *testing.T) {
tests := []struct {
version string
gitCommit string
gitDate string
meta string
expected string
}{
{
version: "v1.0.0",
gitCommit: "c90a760cfaccefb60b942ffe4ccf4f9692587cec",
gitDate: "1698107786",
meta: "",
expected: "v1.0.0-c90a760c-1698107786",
},
{
version: "v1.0.0",
gitCommit: "dev",
gitDate: "1698107786",
meta: "",
expected: "v1.0.0-dev-1698107786",
},
{
version: "v1.0.0",
gitCommit: "",
gitDate: "1698107786",
meta: "",
expected: "v1.0.0-1698107786",
},
{
version: "v1.0.0",
gitCommit: "dev",
gitDate: "",
meta: "",
expected: "v1.0.0-dev",
},
{
version: "v1.0.0",
gitCommit: "",
gitDate: "",
meta: "rc.1",
expected: "v1.0.0-rc.1",
},
{
version: "v1.0.0",
gitCommit: "",
gitDate: "",
meta: "",
expected: "v1.0.0",
},
{
version: "v1.0.0",
gitCommit: "c90a760cfaccefb60b942ffe4ccf4f9692587cec",
gitDate: "1698107786",
meta: "beta",
expected: "v1.0.0-c90a760c-1698107786-beta",
},
}
for _, test := range tests {
test := test
t.Run(test.expected, func(t *testing.T) {
actual := FormatVersion(test.version, test.gitCommit, test.gitDate, test.meta)
require.Equal(t, test.expected, actual)
})
}
}
...@@ -2,9 +2,9 @@ package main ...@@ -2,9 +2,9 @@ package main
import ( import (
"errors" "errors"
"fmt"
"os" "os"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -21,7 +21,7 @@ var ( ...@@ -21,7 +21,7 @@ var (
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate) app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "op-wheel" app.Name = "op-wheel"
app.Usage = "Optimism Wheel is a CLI tool for the execution engine" app.Usage = "Optimism Wheel is a CLI tool for the execution engine"
app.Description = "Optimism Wheel is a CLI tool to direct the engine one way or the other with DB cheats and Engine API routines." app.Description = "Optimism Wheel is a CLI tool to direct the engine one way or the other with DB cheats and Engine API routines."
......
...@@ -11,6 +11,7 @@ import semver ...@@ -11,6 +11,7 @@ import semver
# Minimum version numbers for packages migrating from legacy versioning. # Minimum version numbers for packages migrating from legacy versioning.
MIN_VERSIONS = { MIN_VERSIONS = {
'ci-builder': '0.6.0', 'ci-builder': '0.6.0',
'chain-mon': '0.2.2',
'indexer': '0.5.0', 'indexer': '0.5.0',
'op-node': '0.10.14', 'op-node': '0.10.14',
'op-batcher': '0.10.14', 'op-batcher': '0.10.14',
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
"express-prom-bundle": "^6.6.0", "express-prom-bundle": "^6.6.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"pino": "^8.16.0", "pino": "^8.16.1",
"pino-multi-stream": "^6.0.0", "pino-multi-stream": "^6.0.0",
"pino-sentry": "^0.14.0", "pino-sentry": "^0.14.0",
"prom-client": "^14.2.0" "prom-client": "^14.2.0"
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.8.0", "@typescript-eslint/parser": "^6.9.0",
"tsx": "^3.14.0", "tsx": "^3.14.0",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
"@types/glob": "^8.1.0", "@types/glob": "^8.1.0",
"@vitest/coverage-istanbul": "^0.34.6", "@vitest/coverage-istanbul": "^0.34.6",
"@wagmi/cli": "^1.5.2", "@wagmi/cli": "^1.5.2",
"@wagmi/core": "^1.4.4", "@wagmi/core": "^1.4.5",
"abitype": "^0.10.1", "abitype": "^0.10.1",
"glob": "^10.3.10", "glob": "^10.3.10",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/contracts-ts": "workspace:^", "@eth-optimism/contracts-ts": "workspace:^",
"@swc/core": "^1.3.92", "@swc/core": "^1.3.94",
"@vitest/coverage-istanbul": "^0.34.6", "@vitest/coverage-istanbul": "^0.34.6",
"tsup": "^7.2.0", "tsup": "^7.2.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
......
This diff is collapsed.
...@@ -308,9 +308,9 @@ A reference implementation of the L1 Attributes predeploy contract can be found ...@@ -308,9 +308,9 @@ A reference implementation of the L1 Attributes predeploy contract can be found
[L1Block.sol]: ../packages/contracts-bedrock/src/L2/L1Block.sol [L1Block.sol]: ../packages/contracts-bedrock/src/L2/L1Block.sol
After running `pnpm build` in the `packages/contracts` directory, the bytecode to add to the genesis After running `pnpm build` in the `packages/contracts-bedrock` directory, the bytecode to add to
file will be located in the `deployedBytecode` field of the build artifacts file at the genesis file will be located in the `deployedBytecode` field of the build artifacts file at
`/packages/contracts/artifacts/contracts/L2/L1Block.sol/L1Block.json`. `/packages/contracts-bedrock/forge-artifacts/L1Block.sol/L1Block.json`.
## User-Deposited Transactions ## User-Deposited Transactions
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
You can spin up a local devnet via `docker compose`. You can spin up a local devnet via `docker compose`.
For convenience, we have defined `make` targets to start and stop the devnet with a single command. For convenience, we have defined `make` targets to start and stop the devnet with a single command.
To run the devnet, you will need `docker` installed. To run the devnet, you will need `docker` installed.
Then, as a precondition, make sure that you have compiled the contracts by `cd`ing into `packages/contracts` Then, as a precondition, make sure that you have compiled the contracts by `cd`ing into `packages/contracts-bedrock`
and running `pnpm i` followed by `pnpm build`. You'll only need to do this if you change the contracts in the future. and running `pnpm i` followed by `pnpm build`. You'll only need to do this if you change the contracts in the future.
Then, run the following: Then, run the following:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment