Commit 622780ae authored by Joshua Gutow's avatar Joshua Gutow

Merge branch 'develop' into feat/dump-rollup-config

parents 6250bd76 3b284850
...@@ -192,9 +192,13 @@ jobs: ...@@ -192,9 +192,13 @@ jobs:
description: Run the release script description: Run the release script
type: boolean type: boolean
default: false default: false
resource_class:
description: Docker resoruce class
type: string
default: medium
machine: machine:
image: ubuntu-2204:2022.07.1 image: ubuntu-2204:2022.07.1
resource_class: medium resource_class: "<<parameters.resource_class>>"
docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages
steps: steps:
- checkout - checkout
...@@ -1420,13 +1424,6 @@ workflows: ...@@ -1420,13 +1424,6 @@ workflows:
- op-stack-go-lint - op-stack-go-lint
- devnet-allocs - devnet-allocs
- l1-geth-version-check - l1-geth-version-check
- go-e2e-test:
name: op-e2e-span-batch-tests
module: op-e2e
target: test-span-batch
requires:
- op-stack-go-lint
- devnet-allocs
- op-program-compat: - op-program-compat:
requires: requires:
- op-program-tests - op-program-tests
...@@ -1675,6 +1672,7 @@ workflows: ...@@ -1675,6 +1672,7 @@ workflows:
docker_tags: <<pipeline.git.revision>>,latest docker_tags: <<pipeline.git.revision>>,latest
publish: true publish: true
release: true release: true
resource_class: xlarge
context: context:
- oplabs-gcr - oplabs-gcr
requires: requires:
......
##################################################
# Getting Started #
##################################################
# Admin account
export GS_ADMIN_ADDRESS=
export GS_ADMIN_PRIVATE_KEY=
# Batcher account
export GS_BATCHER_ADDRESS=
export GS_BATCHER_PRIVATE_KEY=
# Proposer account
export GS_PROPOSER_ADDRESS=
export GS_PROPOSER_PRIVATE_KEY=
# Sequencer account
export GS_SEQUENCER_ADDRESS=
export GS_SEQUENCER_PRIVATE_KEY=
##################################################
# op-node Configuration #
##################################################
# The kind of RPC provider, used to inform optimal transactions receipts
# fetching. Valid options: alchemy, quicknode, infura, parity, nethermind,
# debug_geth, erigon, basic, any.
export L1_RPC_KIND=
##################################################
# Contract Deployment #
##################################################
# RPC URL for the L1 network to interact with
export L1_RPC_URL=
# Salt used via CREATE2 to determine implementation addresses
# NOTE: If you want to deploy contracts from scratch you MUST reload this
# variable to ensure the salt is regenerated and the contracts are
# deployed to new addresses (otherwise deployment will fail)
export IMPL_SALT=$(openssl rand -hex 32)
# Name for the deployed network
export DEPLOYMENT_CONTEXT=getting-started
# Optional Tenderly details for simulation link during deployment
export TENDERLY_PROJECT=
export TENDERLY_USERNAME=
# Optional Etherscan API key for contract verification
export ETHERSCAN_API_KEY=
# Private key to use for contract deployments, you don't need to worry about
# this for the Getting Started guide.
export PRIVATE_KEY=
...@@ -30,6 +30,7 @@ packages/contracts-bedrock/deployments/anvil ...@@ -30,6 +30,7 @@ packages/contracts-bedrock/deployments/anvil
.env .env
.env* .env*
!.env.example !.env.example
!.envrc.example
*.log *.log
.devnet .devnet
......
...@@ -7,8 +7,8 @@ toolchain go1.21.1 ...@@ -7,8 +7,8 @@ toolchain go1.21.1
require github.com/ethereum-optimism/optimism v0.0.0 require github.com/ethereum-optimism/optimism v0.0.0
require ( require (
golang.org/x/crypto v0.14.0 // indirect golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.14.0 // indirect
) )
replace github.com/ethereum-optimism/optimism v0.0.0 => ../../.. replace github.com/ethereum-optimism/optimism v0.0.0 => ../../..
...@@ -4,9 +4,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb ...@@ -4,9 +4,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
...@@ -33,7 +33,7 @@ flag_management: ...@@ -33,7 +33,7 @@ flag_management:
individual_flags: individual_flags:
- name: contracts-bedrock-tests - name: contracts-bedrock-tests
paths: paths:
- packages/contracts-bedrock - packages/contracts-bedrock/src
statuses: statuses:
- type: patch - type: patch
target: 100% target: 100%
......
...@@ -33,16 +33,16 @@ require ( ...@@ -33,16 +33,16 @@ 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.29.0 github.com/onsi/gomega v1.30.0
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
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
golang.org/x/crypto v0.14.0 golang.org/x/crypto v0.15.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/sync v0.5.0 golang.org/x/sync v0.5.0
golang.org/x/term v0.13.0 golang.org/x/term v0.14.0
golang.org/x/time v0.4.0 golang.org/x/time v0.4.0
gorm.io/driver/postgres v1.5.4 gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.5
...@@ -199,8 +199,8 @@ require ( ...@@ -199,8 +199,8 @@ require (
go.uber.org/zap v1.26.0 // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/mod v0.13.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
......
...@@ -569,8 +569,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J ...@@ -569,8 +569,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
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.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.30.0/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=
...@@ -769,8 +769,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm ...@@ -769,8 +769,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
...@@ -879,13 +879,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ...@@ -879,13 +879,13 @@ 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=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
...@@ -895,8 +895,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ...@@ -895,8 +895,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
...@@ -51,3 +51,7 @@ export interface WithdrawalResponse { ...@@ -51,3 +51,7 @@ export interface WithdrawalResponse {
hasNextPage: boolean; hasNextPage: boolean;
items: WithdrawalItem[]; items: WithdrawalItem[];
} }
export interface BridgeSupplyView {
l1DepositSum: number /* float64 */;
l2WithdrawalSum: number /* float64 */;
}
...@@ -35,6 +35,8 @@ const ( ...@@ -35,6 +35,8 @@ const (
HealthPath = "/healthz" HealthPath = "/healthz"
DepositsPath = "/api/v0/deposits/" DepositsPath = "/api/v0/deposits/"
WithdrawalsPath = "/api/v0/withdrawals/" WithdrawalsPath = "/api/v0/withdrawals/"
SupplyPath = "/api/v0/supply"
) )
// Api ... Indexer API struct // Api ... Indexer API struct
...@@ -140,15 +142,14 @@ func (a *APIService) initRouter(apiConfig config.ServerConfig) { ...@@ -140,15 +142,14 @@ func (a *APIService) initRouter(apiConfig config.ServerConfig) {
promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace) promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace)
// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder)) apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second)) apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second))
apiRouter.Use(middleware.Recoverer) apiRouter.Use(middleware.Recoverer)
apiRouter.Use(middleware.Heartbeat(HealthPath)) apiRouter.Use(middleware.Heartbeat(HealthPath))
// (3) Set GET routes
apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler) apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler)
apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler) apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler)
apiRouter.Get(SupplyPath, h.SupplyView)
a.router = apiRouter a.router = apiRouter
} }
......
...@@ -95,6 +95,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common. ...@@ -95,6 +95,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
}, },
}, nil }, nil
} }
func (mbv *MockBridgeTransfersView) L1BridgeDepositSum() (float64, error) {
return 69, nil
}
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalSum() (float64, error) {
return 420, nil
}
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
cfg := &Config{ cfg := &Config{
......
...@@ -49,6 +49,11 @@ type WithdrawalResponse struct { ...@@ -49,6 +49,11 @@ type WithdrawalResponse struct {
Items []WithdrawalItem `json:"items"` Items []WithdrawalItem `json:"items"`
} }
type BridgeSupplyView struct {
L1DepositSum float64 `json:"l1DepositSum"`
L2WithdrawalSum float64 `json:"l2WithdrawalSum"`
}
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse // FIXME make a pure function that returns a struct instead of newWithdrawalResponse
// newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse // newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse
func CreateWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse { func CreateWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse {
......
package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/models"
)
// SupplyView ... Handles /api/v0/supply GET requests
func (h Routes) SupplyView(w http.ResponseWriter, r *http.Request) {
depositSum, err := h.view.L1BridgeDepositSum()
if err != nil {
http.Error(w, "internal server error reading deposits", http.StatusInternalServerError)
h.logger.Error("unable to read deposits from DB", "err", err.Error())
return
}
withdrawalSum, err := h.view.L2BridgeWithdrawalSum()
if err != nil {
http.Error(w, "internal server error reading withdrawals", http.StatusInternalServerError)
h.logger.Error("unable to read withdrawals from DB", "err", err.Error())
return
}
view := models.BridgeSupplyView{
L1DepositSum: depositSum,
L2WithdrawalSum: withdrawalSum,
}
err = jsonResponse(w, view, http.StatusOK)
if err != nil {
h.logger.Error("error writing response", "err", err)
}
}
...@@ -23,6 +23,7 @@ const ( ...@@ -23,6 +23,7 @@ const (
healthz = "get_health" healthz = "get_health"
deposits = "get_deposits" deposits = "get_deposits"
withdrawals = "get_withdrawals" withdrawals = "get_withdrawals"
sum = "get_sum"
) )
// Option ... Provides configuration through callback injection // Option ... Provides configuration through callback injection
...@@ -164,6 +165,25 @@ func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]models.Dep ...@@ -164,6 +165,25 @@ func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]models.Dep
} }
// GetSupplyAssessment ... Returns an assessment of the current supply
// on both L1 and L2. This includes the individual sums of
// (L1/L2) deposits and withdrawals
func (c *Client) GetSupplyAssessment() (*models.BridgeSupplyView, error) {
url := c.cfg.BaseURL + api.SupplyPath
resp, err := c.doRecordRequest(sum, url)
if err != nil {
return nil, err
}
var bsv *models.BridgeSupplyView
if err := json.Unmarshal(resp, &bsv); err != nil {
return nil, err
}
return bsv, nil
}
// GetAllWithdrawalsByAddress ... Gets all withdrawals provided a L2 address // GetAllWithdrawalsByAddress ... Gets all withdrawals provided a L2 address
func (c *Client) GetAllWithdrawalsByAddress(l2Address common.Address) ([]models.WithdrawalItem, error) { func (c *Client) GetAllWithdrawalsByAddress(l2Address common.Address) ([]models.WithdrawalItem, error) {
var withdrawals []models.WithdrawalItem var withdrawals []models.WithdrawalItem
......
...@@ -61,10 +61,12 @@ type L2BridgeWithdrawalWithTransactionHashes struct { ...@@ -61,10 +61,12 @@ type L2BridgeWithdrawalWithTransactionHashes struct {
type BridgeTransfersView interface { type BridgeTransfersView interface {
L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error) L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error)
L1BridgeDepositSum() (float64, error)
L1BridgeDepositWithFilter(BridgeTransfer) (*L1BridgeDeposit, error) L1BridgeDepositWithFilter(BridgeTransfer) (*L1BridgeDeposit, error)
L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error) L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error)
L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error) L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalSum() (float64, error)
L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error) L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error) L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error)
} }
...@@ -136,6 +138,17 @@ type L1BridgeDepositsResponse struct { ...@@ -136,6 +138,17 @@ type L1BridgeDepositsResponse struct {
HasNextPage bool HasNextPage bool
} }
// L1BridgeDepositSum ... returns the sum of all l1 bridge deposit mints in gwei
func (db *bridgeTransfersDB) L1BridgeDepositSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L1TransactionDeposit{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}
return sum, nil
}
// L1BridgeDepositsByAddress retrieves a list of deposits initiated by the specified address, // L1BridgeDepositsByAddress retrieves a list of deposits initiated by the specified address,
// coupled with the L1/L2 transaction hashes that complete the bridge transaction. // coupled with the L1/L2 transaction hashes that complete the bridge transaction.
func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*L1BridgeDepositsResponse, error) { func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*L1BridgeDepositsResponse, error) {
...@@ -233,6 +246,16 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (* ...@@ -233,6 +246,16 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (*
return &withdrawal, nil return &withdrawal, nil
} }
func (db *bridgeTransfersDB) L2BridgeWithdrawalSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L2TransactionWithdrawal{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}
return sum, nil
}
// L2BridgeWithdrawalWithFilter queries for a bridge withdrawal with set fields in the `BridgeTransfer` filter // L2BridgeWithdrawalWithFilter queries for a bridge withdrawal with set fields in the `BridgeTransfer` filter
func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) (*L2BridgeWithdrawal, error) { func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) (*L2BridgeWithdrawal, error) {
var withdrawal L2BridgeWithdrawal var withdrawal L2BridgeWithdrawal
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/indexer/bigint"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils" e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
...@@ -447,7 +448,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) { ...@@ -447,7 +448,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
} }
} }
func TestClientGetWithdrawals(t *testing.T) { func TestClientBridgeFunctions(t *testing.T) {
testSuite := createE2ETestSuite(t) testSuite := createE2ETestSuite(t)
// (1) Generate contract bindings for the L1 and L2 standard bridges // (1) Generate contract bindings for the L1 and L2 standard bridges
...@@ -459,12 +460,16 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -459,12 +460,16 @@ func TestClientGetWithdrawals(t *testing.T) {
// (2) Create test actors that will deposit and withdraw using the standard bridge // (2) Create test actors that will deposit and withdraw using the standard bridge
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
bobAddr := testSuite.OpCfg.Secrets.Addresses().Bob bobAddr := testSuite.OpCfg.Secrets.Addresses().Bob
malAddr := testSuite.OpCfg.Secrets.Addresses().Mallory
type actor struct { type actor struct {
addr common.Address addr common.Address
priv *ecdsa.PrivateKey priv *ecdsa.PrivateKey
} }
mintSum := bigint.Zero
withdrawSum := bigint.Zero
actors := []actor{ actors := []actor{
{ {
addr: aliceAddr, addr: aliceAddr,
...@@ -474,6 +479,10 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -474,6 +479,10 @@ func TestClientGetWithdrawals(t *testing.T) {
addr: bobAddr, addr: bobAddr,
priv: testSuite.OpCfg.Secrets.Bob, priv: testSuite.OpCfg.Secrets.Bob,
}, },
{
addr: malAddr,
priv: testSuite.OpCfg.Secrets.Mallory,
},
} }
// (3) Iterate over each actor and deposit / withdraw // (3) Iterate over each actor and deposit / withdraw
...@@ -491,6 +500,8 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -491,6 +500,8 @@ func TestClientGetWithdrawals(t *testing.T) {
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash()) _, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err) require.NoError(t, err)
mintSum = new(big.Int).Add(mintSum, depositTx.Value())
// (3.b) Initiate withdrawal transaction via L2ToL1MessagePasser contract // (3.b) Initiate withdrawal transaction via L2ToL1MessagePasser contract
l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts) l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts)
require.NoError(t, err) require.NoError(t, err)
...@@ -503,6 +514,8 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -503,6 +514,8 @@ func TestClientGetWithdrawals(t *testing.T) {
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
})) }))
withdrawSum = new(big.Int).Add(withdrawSum, l2ToL1MessagePasserWithdrawTx.Value())
// (3.d) Ensure that withdrawal and deposit txs are retrievable via API // (3.d) Ensure that withdrawal and deposit txs are retrievable via API
deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr) deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr)
require.NoError(t, err) require.NoError(t, err)
...@@ -513,6 +526,17 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -513,6 +526,17 @@ func TestClientGetWithdrawals(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, withdrawals, 1) require.Len(t, withdrawals, 1)
require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), withdrawals[0].TransactionHash) require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), withdrawals[0].TransactionHash)
} }
// (4) Ensure that supply assessment is correct
assessment, err := testSuite.Client.GetSupplyAssessment()
require.NoError(t, err)
mintFloat, _ := mintSum.Float64()
require.Equal(t, mintFloat, assessment.L1DepositSum)
withdrawFloat, _ := withdrawSum.Float64()
require.Equal(t, withdrawFloat, assessment.L2WithdrawalSum)
} }
...@@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits ( ...@@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits (
-- transaction data. NOTE: `to_address` is the recipient of funds transferred in value field of the -- transaction data. NOTE: `to_address` is the recipient of funds transferred in value field of the
-- L2 deposit transaction and not the amount minted on L1 from the source address. Hence the `amount` -- L2 deposit transaction and not the amount minted on L1 from the source address. Hence the `amount`
-- column in this table does NOT indiciate the amount transferred to the recipient but instead funds -- column in this table does NOT indicate the amount transferred to the recipient but instead funds
-- bridged from L1 into `from_address`. -- bridged from L1 by the `from_address`.
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -13,7 +13,7 @@ const StorageSetterStorageLayoutJSON = "{\"storage\":null,\"types\":{}}" ...@@ -13,7 +13,7 @@ const StorageSetterStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
var StorageSetterStorageLayout = new(solc.StorageLayout) var StorageSetterStorageLayout = new(solc.StorageLayout)
var StorageSetterDeployedBin = "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a6ed563e1161005b578063a6ed563e1461011c578063bd02d0f51461011c578063ca446dd914610138578063e2a4853a146100bf57600080fd5b806321f8a721146100825780634e91db08146100bf57806354fd4d50146100d3575b600080fd5b610095610090366004610156565b610146565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d16100cd36600461016f565b9055565b005b61010f6040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100b69190610191565b61012a610090366004610156565b6040519081526020016100b6565b6100d16100cd366004610204565b6000610150825490565b92915050565b60006020828403121561016857600080fd5b5035919050565b6000806040838503121561018257600080fd5b50508035926020909101359150565b600060208083528351808285015260005b818110156101be578581018301518582016040015282016101a2565b818111156101d0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561021757600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461024257600080fd5b80915050925092905056fea164736f6c634300080f000a" var StorageSetterDeployedBin = "0x608060405234801561001057600080fd5b50600436106100885760003560e01c8063a6ed563e1161005b578063a6ed563e1461013a578063bd02d0f51461013a578063ca446dd914610156578063e2a4853a146100df57600080fd5b80630528afe21461008d57806321f8a721146100a25780634e91db08146100df57806354fd4d50146100f1575b600080fd5b6100a061009b3660046101d7565b610164565b005b6100b56100b036600461024c565b6101c7565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100a06100ed366004610265565b9055565b61012d6040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100d69190610287565b6101486100b036600461024c565b6040519081526020016100d6565b6100a06100ed3660046102fa565b8060005b818110156101c1576101af84848381811061018557610185610343565b905060400201600001358585848181106101a1576101a1610343565b905060400201602001359055565b806101b981610372565b915050610168565b50505050565b60006101d1825490565b92915050565b600080602083850312156101ea57600080fd5b823567ffffffffffffffff8082111561020257600080fd5b818501915085601f83011261021657600080fd5b81358181111561022557600080fd5b8660208260061b850101111561023a57600080fd5b60209290920196919550909350505050565b60006020828403121561025e57600080fd5b5035919050565b6000806040838503121561027857600080fd5b50508035926020909101359150565b600060208083528351808285015260005b818110156102b457858101830151858201604001528201610298565b818111156102c6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561030d57600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461033857600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036103ca577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b506001019056fea164736f6c634300080f000a"
func init() { func init() {
if err := json.Unmarshal([]byte(StorageSetterStorageLayoutJSON), StorageSetterStorageLayout); err != nil { if err := json.Unmarshal([]byte(StorageSetterStorageLayoutJSON), StorageSetterStorageLayout); err != nil {
......
package main
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"math/big"
"os"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
)
func CalcBaseFee(parent eth.BlockInfo, elasticity uint64, canyonActive bool) *big.Int {
denomUint := uint64(50)
if canyonActive {
denomUint = uint64(250)
}
parentGasTarget := parent.GasLimit() / elasticity
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if parent.GasUsed() == parentGasTarget {
return new(big.Int).Set(parent.BaseFee())
}
var (
num = new(big.Int)
denom = new(big.Int)
)
if parent.GasUsed() > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parent.GasUsed() - parentGasTarget)
num.Mul(num, parent.BaseFee())
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(denomUint))
baseFeeDelta := math.BigMax(num, common.Big1)
return num.Add(parent.BaseFee(), baseFeeDelta)
} else {
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parentGasTarget - parent.GasUsed())
num.Mul(num, parent.BaseFee())
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(denomUint))
baseFee := num.Sub(parent.BaseFee(), num)
return math.BigMax(baseFee, common.Big0)
}
}
func ManuallyEncodeReceipts(receipts types.Receipts, canyonActive bool) [][]byte {
v := uint64(1)
for _, receipt := range receipts {
if receipt.Type == types.DepositTxType {
if canyonActive {
receipt.DepositReceiptVersion = &v
} else {
receipt.DepositReceiptVersion = nil
}
}
}
var out [][]byte
for i := range receipts {
var buf bytes.Buffer
receipts.EncodeIndex(i, &buf)
out = append(out, buf.Bytes())
}
return out
}
type rawReceipts [][]byte
func (s rawReceipts) Len() int { return len(s) }
func (s rawReceipts) EncodeIndex(i int, w *bytes.Buffer) {
w.Write(s[i])
}
func HashList(list [][]byte) common.Hash {
hasher := trie.NewStackTrie(nil)
return types.DeriveSha(rawReceipts(list), hasher)
}
type L2Client interface {
BlockByNumber(context.Context, *big.Int) (*types.Block, error)
CodeAt(context.Context, common.Address, *big.Int) ([]byte, error)
InfoByNumber(context.Context, uint64) (eth.BlockInfo, error)
FetchReceipts(context.Context, common.Hash) (eth.BlockInfo, types.Receipts, error)
}
type Client struct {
*ethclient.Client
*sources.L1Client
}
type Args struct {
Number uint64
Elasticity uint64
Client L2Client
}
func ValidateReceipts(ctx Args, canyonActive bool) error {
block, err := ctx.Client.InfoByNumber(context.Background(), ctx.Number)
if err != nil {
return err
}
_, receipts, err := ctx.Client.FetchReceipts(context.Background(), block.Hash())
if err != nil {
return err
}
have := block.ReceiptHash()
want := HashList(ManuallyEncodeReceipts(receipts, canyonActive))
if have != want {
return fmt.Errorf("Receipts do not look correct. canyonActive: %v. have: %v, want: %v", canyonActive, have, want)
}
return nil
}
func Validate1559Params(ctx Args, canyonActive bool) error {
block, err := ctx.Client.InfoByNumber(context.Background(), ctx.Number)
if err != nil {
return err
}
parent, err := ctx.Client.InfoByNumber(context.Background(), ctx.Number-1)
if err != nil {
return err
}
want := CalcBaseFee(parent, ctx.Elasticity, canyonActive)
have := block.BaseFee()
if have.Cmp(want) != 0 {
return fmt.Errorf("BaseFee does not match. canyonActive: %v. have: %v, want: %v", canyonActive, have, want)
}
return nil
}
func ValidateWithdrawals(ctx Args, canyonActive bool) error {
block, err := ctx.Client.BlockByNumber(context.Background(), new(big.Int).SetUint64(ctx.Number))
if err != nil {
return err
}
if canyonActive && block.Withdrawals() == nil {
return errors.New("No nonwithdrawals in a canyon block")
} else if canyonActive && len(block.Withdrawals()) > 0 {
return errors.New("Withdrawals length is not zero in a canyon block")
} else if !canyonActive && block.Withdrawals() != nil {
return errors.New("Withdrawals in a pre-canyon block")
}
return nil
}
func ValidateCreate2Deployer(ctx Args, canyonActive bool) error {
addr := common.HexToAddress("0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2")
code, err := ctx.Client.CodeAt(context.Background(), addr, new(big.Int).SetUint64(ctx.Number))
if err != nil {
return err
}
codeHash := crypto.Keccak256Hash(code)
expectedCodeHash := common.HexToHash("0xb0550b5b431e30d38000efb7107aaa0ade03d48a7198a140edda9d27134468b2")
if canyonActive && codeHash != expectedCodeHash {
return fmt.Errorf("Canyon active but code hash does not match. have: %v, want: %v", codeHash, expectedCodeHash)
} else if !canyonActive && codeHash == expectedCodeHash {
return fmt.Errorf("Canyon not active but code hashes do match. codeHash: %v", codeHash)
}
return nil
}
// CheckActivation takes a function f which determines in a specific block follows the rules of a fork.
func CheckActivation(f func(Args, bool) error, ctx Args, forkActivated bool, valid *bool) {
if err := f(ctx, forkActivated); err != nil {
log.Error("Block did not follow fork rules", "err", err)
*valid = false
}
}
// CheckInactivation takes a function f which determines in a specific block follows the rules of a fork.
// It passes the oppose value of forkActivated & asserts that an error is returned.
func CheckInactivation(f func(Args, bool) error, ctx Args, forkActivated bool, valid *bool) {
if err := f(ctx, !forkActivated); err == nil {
log.Error("Block followed the wrong side of the fork rules")
*valid = false
}
}
func main() {
logger := log.New()
// Define the flag variables
var (
canyonActive bool
number uint64
elasticity uint64
rpcURL string
)
valid := true
// Define and parse the command-line flags
flag.BoolVar(&canyonActive, "canyon", false, "Set this flag to assert canyon behavior")
flag.Uint64Var(&number, "number", 31, "Block number to check")
flag.Uint64Var(&elasticity, "elasticity", 6, "Specify the EIP-1559 elasticity. 6 on mainnet/sepolia. 10 on goerli")
flag.StringVar(&rpcURL, "rpc-url", "http://localhost:8545", "Specify the L2 ETH RPC URL")
// Parse the command-line arguments
flag.Parse()
l2RPC, err := client.NewRPC(context.Background(), logger, rpcURL, client.WithDialBackoff(10))
if err != nil {
log.Crit("Error creating RPC", "err", err)
}
c := &rollup.Config{SeqWindowSize: 10}
l2Cfg := sources.L1ClientDefaultConfig(c, true, sources.RPCKindBasic)
sourceClient, err := sources.NewL1Client(l2RPC, logger, nil, l2Cfg)
if err != nil {
log.Crit("Error creating RPC", "err", err)
}
ethClient, err := ethclient.Dial(rpcURL)
if err != nil {
log.Crit("Error creating RPC", "err", err)
}
client := Client{ethClient, sourceClient}
ctx := Args{
Number: number,
Elasticity: elasticity,
Client: client,
}
CheckActivation(ValidateReceipts, ctx, canyonActive, &valid)
CheckInactivation(ValidateReceipts, ctx, canyonActive, &valid)
CheckActivation(Validate1559Params, ctx, canyonActive, &valid)
// Don't check in-activation for 1559 b/c at low basefees the two cannot be differentiated
CheckActivation(ValidateWithdrawals, ctx, canyonActive, &valid)
CheckInactivation(ValidateWithdrawals, ctx, canyonActive, &valid)
CheckActivation(ValidateCreate2Deployer, ctx, canyonActive, &valid)
CheckInactivation(ValidateCreate2Deployer, ctx, canyonActive, &valid)
if !valid {
os.Exit(1)
} else if canyonActive {
log.Info(fmt.Sprintf("Successfully validated block %v as a Canyon block", number))
} else {
log.Info(fmt.Sprintf("Successfully validated block %v as a Pre-Canyon block", number))
}
}
...@@ -388,41 +388,28 @@ func (d *DeployConfig) SetDeployments(deployments *L1Deployments) { ...@@ -388,41 +388,28 @@ func (d *DeployConfig) SetDeployments(deployments *L1Deployments) {
} }
// GetDeployedAddresses will get the deployed addresses of deployed L1 contracts // GetDeployedAddresses will get the deployed addresses of deployed L1 contracts
// required for the L2 genesis creation. Legacy systems use the `Proxy__` prefix // required for the L2 genesis creation.
// while modern systems use the `Proxy` suffix. First check for the legacy
// deployments so that this works with upgrading a system.
func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error { func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
var err error
if d.L1StandardBridgeProxy == (common.Address{}) { if d.L1StandardBridgeProxy == (common.Address{}) {
var l1StandardBridgeProxyDeployment *hardhat.Deployment l1StandardBridgeProxyDeployment, err := hh.GetDeployment("L1StandardBridgeProxy")
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1StandardBridge")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("L1StandardBridgeProxy")
if err != nil { if err != nil {
return err return fmt.Errorf("cannot find L1StandardBridgeProxy artifact: %w", err)
}
} }
d.L1StandardBridgeProxy = l1StandardBridgeProxyDeployment.Address d.L1StandardBridgeProxy = l1StandardBridgeProxyDeployment.Address
} }
if d.L1CrossDomainMessengerProxy == (common.Address{}) { if d.L1CrossDomainMessengerProxy == (common.Address{}) {
var l1CrossDomainMessengerProxyDeployment *hardhat.Deployment l1CrossDomainMessengerProxyDeployment, err := hh.GetDeployment("L1CrossDomainMessengerProxy")
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1CrossDomainMessenger")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("L1CrossDomainMessengerProxy")
if err != nil { if err != nil {
return err return fmt.Errorf("cannot find L1CrossDomainMessengerProxy artifact: %w", err)
}
} }
d.L1CrossDomainMessengerProxy = l1CrossDomainMessengerProxyDeployment.Address d.L1CrossDomainMessengerProxy = l1CrossDomainMessengerProxyDeployment.Address
} }
if d.L1ERC721BridgeProxy == (common.Address{}) { if d.L1ERC721BridgeProxy == (common.Address{}) {
// There is no legacy deployment of this contract
l1ERC721BridgeProxyDeployment, err := hh.GetDeployment("L1ERC721BridgeProxy") l1ERC721BridgeProxyDeployment, err := hh.GetDeployment("L1ERC721BridgeProxy")
if err != nil { if err != nil {
return err return fmt.Errorf("cannot find L1ERC721BridgeProxy artifact: %w", err)
} }
d.L1ERC721BridgeProxy = l1ERC721BridgeProxyDeployment.Address d.L1ERC721BridgeProxy = l1ERC721BridgeProxyDeployment.Address
} }
...@@ -430,7 +417,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error { ...@@ -430,7 +417,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
if d.SystemConfigProxy == (common.Address{}) { if d.SystemConfigProxy == (common.Address{}) {
systemConfigProxyDeployment, err := hh.GetDeployment("SystemConfigProxy") systemConfigProxyDeployment, err := hh.GetDeployment("SystemConfigProxy")
if err != nil { if err != nil {
return err return fmt.Errorf("cannot find SystemConfigProxy artifact: %w", err)
} }
d.SystemConfigProxy = systemConfigProxyDeployment.Address d.SystemConfigProxy = systemConfigProxyDeployment.Address
} }
...@@ -438,7 +425,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error { ...@@ -438,7 +425,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
if d.OptimismPortalProxy == (common.Address{}) { if d.OptimismPortalProxy == (common.Address{}) {
optimismPortalProxyDeployment, err := hh.GetDeployment("OptimismPortalProxy") optimismPortalProxyDeployment, err := hh.GetDeployment("OptimismPortalProxy")
if err != nil { if err != nil {
return err return fmt.Errorf("cannot find OptimismPortalProxy artifact: %w", err)
} }
d.OptimismPortalProxy = optimismPortalProxyDeployment.Address d.OptimismPortalProxy = optimismPortalProxyDeployment.Address
} }
......
...@@ -39,7 +39,7 @@ type Agent struct { ...@@ -39,7 +39,7 @@ type Agent struct {
log log.Logger log log.Logger
} }
func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceProvider, responder Responder, updater types.OracleUpdater, agreeWithProposedOutput bool, log log.Logger) *Agent { func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceAccessor, responder Responder, updater types.OracleUpdater, agreeWithProposedOutput bool, log log.Logger) *Agent {
return &Agent{ return &Agent{
metrics: m, metrics: m,
solver: solver.NewGameSolver(maxDepth, trace), solver: solver.NewGameSolver(maxDepth, trace),
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -112,10 +113,10 @@ func setupTestAgent(t *testing.T, agreeWithProposedOutput bool) (*Agent, *stubCl ...@@ -112,10 +113,10 @@ func setupTestAgent(t *testing.T, agreeWithProposedOutput bool) (*Agent, *stubCl
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
claimLoader := &stubClaimLoader{} claimLoader := &stubClaimLoader{}
depth := 4 depth := 4
trace := alphabet.NewTraceProvider("abcd", uint64(depth)) provider := alphabet.NewTraceProvider("abcd", uint64(depth))
responder := &stubResponder{} responder := &stubResponder{}
updater := &stubUpdater{} updater := &stubUpdater{}
agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace, responder, updater, agreeWithProposedOutput, logger) agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace.NewSimpleTraceAccessor(provider), responder, updater, agreeWithProposedOutput, logger)
return agent, claimLoader, responder return agent, claimLoader, responder
} }
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
...@@ -19,6 +20,13 @@ const ( ...@@ -19,6 +20,13 @@ const (
methodStatus = "status" methodStatus = "status"
methodClaimCount = "claimDataLen" methodClaimCount = "claimDataLen"
methodClaim = "claimData" methodClaim = "claimData"
methodL1Head = "l1Head"
methodProposals = "proposals"
methodResolve = "resolve"
methodResolveClaim = "resolveClaim"
methodAttack = "attack"
methodDefend = "defend"
methodStep = "step"
) )
type FaultDisputeGameContract struct { type FaultDisputeGameContract struct {
...@@ -26,6 +34,12 @@ type FaultDisputeGameContract struct { ...@@ -26,6 +34,12 @@ type FaultDisputeGameContract struct {
contract *batching.BoundContract contract *batching.BoundContract
} }
type Proposal struct {
Index *big.Int
L2BlockNumber *big.Int
OutputRoot common.Hash
}
func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) { func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi() fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil { if err != nil {
...@@ -62,6 +76,27 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) ...@@ -62,6 +76,27 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context)
return result.GetHash(0), nil return result.GetHash(0), nil
} }
func (f *FaultDisputeGameContract) GetL1Head(ctx context.Context) (common.Hash, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodL1Head))
if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch L1 head: %w", err)
}
return result.GetHash(0), nil
}
// GetProposals returns the agreed and disputed proposals
func (f *FaultDisputeGameContract) GetProposals(ctx context.Context) (Proposal, Proposal, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodProposals))
if err != nil {
return Proposal{}, Proposal{}, fmt.Errorf("failed to fetch proposals: %w", err)
}
var agreed, disputed Proposal
result.GetStruct(0, &agreed)
result.GetStruct(1, &disputed)
return agreed, disputed, nil
}
func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) { func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodStatus)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodStatus))
if err != nil { if err != nil {
...@@ -109,6 +144,57 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl ...@@ -109,6 +144,57 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
return claims, nil return claims, nil
} }
func (f *FaultDisputeGameContract) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodDefend, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodStep, new(big.Int).SetUint64(claimIdx), isAttack, stateData, proof)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) CallResolveClaim(ctx context.Context, claimIdx uint64) error {
call := f.resolveClaimCall(claimIdx)
_, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, call)
if err != nil {
return fmt.Errorf("failed to call resolve claim: %w", err)
}
return nil
}
func (f *FaultDisputeGameContract) ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) {
call := f.resolveClaimCall(claimIdx)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) resolveClaimCall(claimIdx uint64) *batching.ContractCall {
return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx))
}
func (f *FaultDisputeGameContract) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
call := f.resolveCall()
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, call)
if err != nil {
return gameTypes.GameStatusInProgress, fmt.Errorf("failed to call resolve: %w", err)
}
return gameTypes.GameStatusFromUint8(result.GetUint8(0))
}
func (f *FaultDisputeGameContract) ResolveTx() (txmgr.TxCandidate, error) {
call := f.resolveCall()
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall {
return f.contract.Call(methodResolve)
}
func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim { func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0) parentIndex := result.GetUint32(0)
countered := result.GetBool(1) countered := result.GetBool(1)
......
...@@ -60,6 +60,20 @@ func TestSimpleGetters(t *testing.T) { ...@@ -60,6 +60,20 @@ func TestSimpleGetters(t *testing.T) {
return game.GetClaimCount(context.Background()) return game.GetClaimCount(context.Background())
}, },
}, },
{
method: methodL1Head,
result: common.Hash{0xdd, 0xbb},
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetL1Head(context.Background())
},
},
{
method: methodResolve,
result: types.GameStatusInProgress,
call: func(game *FaultDisputeGameContract) (any, error) {
return game.CallResolve(context.Background())
},
},
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
...@@ -77,6 +91,33 @@ func TestSimpleGetters(t *testing.T) { ...@@ -77,6 +91,33 @@ func TestSimpleGetters(t *testing.T) {
} }
} }
func TestGetProposals(t *testing.T) {
stubRpc, game := setup(t)
agreedIndex := big.NewInt(5)
agreedBlockNum := big.NewInt(6)
agreedRoot := common.Hash{0xaa}
disputedIndex := big.NewInt(7)
disputedBlockNum := big.NewInt(8)
disputedRoot := common.Hash{0xdd}
agreed := Proposal{
Index: agreedIndex,
L2BlockNumber: agreedBlockNum,
OutputRoot: agreedRoot,
}
disputed := Proposal{
Index: disputedIndex,
L2BlockNumber: disputedBlockNum,
OutputRoot: disputedRoot,
}
stubRpc.SetResponse(methodProposals, batching.BlockLatest, []interface{}{}, []interface{}{
agreed, disputed,
})
actualAgreed, actualDisputed, err := game.GetProposals(context.Background())
require.NoError(t, err)
require.Equal(t, agreed, actualAgreed)
require.Equal(t, disputed, actualDisputed)
}
func TestGetClaim(t *testing.T) { func TestGetClaim(t *testing.T) {
stubRpc, game := setup(t) stubRpc, game := setup(t)
idx := big.NewInt(2) idx := big.NewInt(2)
...@@ -142,6 +183,57 @@ func TestGetAllClaims(t *testing.T) { ...@@ -142,6 +183,57 @@ func TestGetAllClaims(t *testing.T) {
require.Equal(t, expectedClaims, claims) require.Equal(t, expectedClaims, claims)
} }
func TestCallResolveClaim(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
err := game.CallResolveClaim(context.Background(), 123)
require.NoError(t, err)
}
func TestResolveClaimTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
tx, err := game.ResolveClaimTx(123)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestResolveTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolve, batching.BlockLatest, nil, nil)
tx, err := game.ResolveTx()
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestAttackTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(methodAttack, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.AttackTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestDefendTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(methodDefend, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.DefendTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestStepTx(t *testing.T) {
stubRpc, game := setup(t)
stateData := []byte{1, 2, 3}
proofData := []byte{4, 5, 6, 7, 8, 9}
stubRpc.SetResponse(methodStep, batching.BlockLatest, []interface{}{big.NewInt(111), true, stateData, proofData}, nil)
tx, err := game.StepTx(111, true, stateData, proofData)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) { func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
stubRpc.SetResponse( stubRpc.SetResponse(
methodClaim, methodClaim,
......
...@@ -25,6 +25,10 @@ type GameInfo interface { ...@@ -25,6 +25,10 @@ type GameInfo interface {
GetClaimCount(context.Context) (uint64, error) GetClaimCount(context.Context) (uint64, error)
} }
// gameValidator checks that the specific game instance is compatible with the configuration.
// Typically, this is done by verifying the absolute prestate of the game matches the local absolute prestate.
type gameValidator func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error
type GamePlayer struct { type GamePlayer struct {
act actor act actor
agreeWithProposedOutput bool agreeWithProposedOutput bool
...@@ -33,7 +37,7 @@ type GamePlayer struct { ...@@ -33,7 +37,7 @@ type GamePlayer struct {
status gameTypes.GameStatus status gameTypes.GameStatus
} }
type resourceCreator func(addr common.Address, gameDepth uint64, dir string) (types.TraceProvider, types.OracleUpdater, error) type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, types.OracleUpdater, gameValidator, error)
func NewGamePlayer( func NewGamePlayer(
ctx context.Context, ctx context.Context,
...@@ -76,22 +80,23 @@ func NewGamePlayer( ...@@ -76,22 +80,23 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to fetch the game depth: %w", err) return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
} }
provider, updater, err := creator(addr, gameDepth, dir) accessor, updater, validator, err := creator(addr, loader, gameDepth, dir)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create trace provider: %w", err) return nil, fmt.Errorf("failed to create trace accessor: %w", err)
} }
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil { if err := validator(ctx, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err) return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
} }
responder, err := responder.NewFaultResponder(logger, txMgr, addr) responder, err := responder.NewFaultResponder(logger, txMgr, loader)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err) return nil, fmt.Errorf("failed to create the responder: %w", err)
} }
agent := NewAgent(m, loader, int(gameDepth), accessor, responder, updater, cfg.AgreeWithProposedOutput, logger)
return &GamePlayer{ return &GamePlayer{
act: NewAgent(m, loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, logger).Act, act: agent.Act,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput, agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
loader: loader, loader: loader,
logger: logger, logger: logger,
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
...@@ -36,16 +38,19 @@ func RegisterGameTypes( ...@@ -36,16 +38,19 @@ func RegisterGameTypes(
client *ethclient.Client, client *ethclient.Client,
) { ) {
if cfg.TraceTypeEnabled(config.TraceTypeCannon) { if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
resourceCreator := func(addr common.Address, gameDepth uint64, dir string) (faultTypes.TraceProvider, faultTypes.OracleUpdater, error) { resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) {
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, client, dir, addr, gameDepth) provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, dir, gameDepth)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err) return nil, nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
} }
updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client) updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err) return nil, nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err)
} }
return provider, updater, nil validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return trace.NewSimpleTraceAccessor(provider), updater, validator, nil
} }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator) return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
...@@ -53,10 +58,13 @@ func RegisterGameTypes( ...@@ -53,10 +58,13 @@ func RegisterGameTypes(
registry.RegisterGameType(cannonGameType, playerCreator) registry.RegisterGameType(cannonGameType, playerCreator)
} }
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) { if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
resourceCreator := func(addr common.Address, gameDepth uint64, dir string) (faultTypes.TraceProvider, faultTypes.OracleUpdater, error) { resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) {
provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth) provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater := alphabet.NewOracleUpdater(logger) updater := alphabet.NewOracleUpdater(logger)
return provider, updater, nil validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return trace.NewSimpleTraceAccessor(provider), updater, validator, nil
} }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator) return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......
package responder
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// setupFaultDisputeGame deploys the FaultDisputeGame contract to a simulated backend
func setupFaultDisputeGame() (common.Address, *bind.TransactOpts, *backends.SimulatedBackend, *bindings.FaultDisputeGame, error) {
privateKey, err := crypto.GenerateKey()
from := crypto.PubkeyToAddress(privateKey.PublicKey)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337))
if err != nil {
return common.Address{}, nil, nil, nil, err
}
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{from: {Balance: big.NewInt(params.Ether)}},
50_000_000,
)
blockHashOracle, _, _, err := bindings.DeployBlockOracle(opts, backend)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
_, _, contract, err := bindings.DeployFaultDisputeGame(
opts,
backend,
uint8(0), // Game Type ID
[32]byte{0x01}, // Absolute Prestate Claim
big.NewInt(15), // Max Game Depth
uint64(604800), // 7 days
common.Address{0xdd}, // VM
common.Address{0xee}, // L2OutputOracle (Not used in Alphabet Game)
blockHashOracle, // Block hash oracle
)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
return from, opts, backend, contract, nil
}
// TestBuildFaultDefendData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultDefendData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildFaultDefendData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Defend(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultAttackData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultAttackData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildFaultAttackData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Attack(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultStepData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultStepData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildStepTxData(2, false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Step(opts, big.NewInt(2), false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
...@@ -2,153 +2,97 @@ package responder ...@@ -2,153 +2,97 @@ package responder
import ( import (
"context" "context"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type GameContract interface {
CallResolve(ctx context.Context) (gameTypes.GameStatus, error)
ResolveTx() (txmgr.TxCandidate, error)
CallResolveClaim(ctx context.Context, claimIdx uint64) error
ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error)
AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
}
// FaultResponder implements the [Responder] interface to send onchain transactions. // FaultResponder implements the [Responder] interface to send onchain transactions.
type FaultResponder struct { type FaultResponder struct {
log log.Logger log log.Logger
txMgr txmgr.TxManager txMgr txmgr.TxManager
contract GameContract
fdgAddr common.Address
fdgAbi *abi.ABI
} }
// NewFaultResponder returns a new [FaultResponder]. // NewFaultResponder returns a new [FaultResponder].
func NewFaultResponder(logger log.Logger, txManagr txmgr.TxManager, fdgAddr common.Address) (*FaultResponder, error) { func NewFaultResponder(logger log.Logger, txMgr txmgr.TxManager, contract GameContract) (*FaultResponder, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
return nil, err
}
return &FaultResponder{ return &FaultResponder{
log: logger, log: logger,
txMgr: txManagr, txMgr: txMgr,
fdgAddr: fdgAddr, contract: contract,
fdgAbi: fdgAbi,
}, nil }, nil
} }
// buildFaultDefendData creates the transaction data for the Defend function.
func (r *FaultResponder) buildFaultDefendData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"defend",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// buildFaultAttackData creates the transaction data for the Attack function.
func (r *FaultResponder) buildFaultAttackData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"attack",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// buildResolveData creates the transaction data for the Resolve function.
func (r *FaultResponder) buildResolveData() ([]byte, error) {
return r.fdgAbi.Pack("resolve")
}
// CallResolve determines if the resolve function on the fault dispute game contract // CallResolve determines if the resolve function on the fault dispute game contract
// would succeed. Returns the game status if the call would succeed, errors otherwise. // would succeed. Returns the game status if the call would succeed, errors otherwise.
func (r *FaultResponder) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) { func (r *FaultResponder) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
txData, err := r.buildResolveData() return r.contract.CallResolve(ctx)
if err != nil {
return gameTypes.GameStatusInProgress, err
}
res, err := r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr,
Data: txData,
}, nil)
if err != nil {
return gameTypes.GameStatusInProgress, err
}
var status uint8
if err = r.fdgAbi.UnpackIntoInterface(&status, "resolve", res); err != nil {
return gameTypes.GameStatusInProgress, err
}
return gameTypes.GameStatusFromUint8(status)
} }
// Resolve executes a resolve transaction to resolve a fault dispute game. // Resolve executes a resolve transaction to resolve a fault dispute game.
func (r *FaultResponder) Resolve(ctx context.Context) error { func (r *FaultResponder) Resolve(ctx context.Context) error {
txData, err := r.buildResolveData() candidate, err := r.contract.ResolveTx()
if err != nil { if err != nil {
return err return err
} }
return r.sendTxAndWait(ctx, txData) return r.sendTxAndWait(ctx, candidate)
}
// buildResolveClaimData creates the transaction data for the ResolveClaim function.
func (r *FaultResponder) buildResolveClaimData(claimIdx uint64) ([]byte, error) {
return r.fdgAbi.Pack("resolveClaim", big.NewInt(int64(claimIdx)))
} }
// CallResolveClaim determines if the resolveClaim function on the fault dispute game contract // CallResolveClaim determines if the resolveClaim function on the fault dispute game contract
// would succeed. // would succeed.
func (r *FaultResponder) CallResolveClaim(ctx context.Context, claimIdx uint64) error { func (r *FaultResponder) CallResolveClaim(ctx context.Context, claimIdx uint64) error {
txData, err := r.buildResolveClaimData(claimIdx) return r.contract.CallResolveClaim(ctx, claimIdx)
if err != nil {
return err
}
_, err = r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr,
Data: txData,
}, nil)
return err
} }
// ResolveClaim executes a resolveClaim transaction to resolve a fault dispute game. // ResolveClaim executes a resolveClaim transaction to resolve a fault dispute game.
func (r *FaultResponder) ResolveClaim(ctx context.Context, claimIdx uint64) error { func (r *FaultResponder) ResolveClaim(ctx context.Context, claimIdx uint64) error {
txData, err := r.buildResolveClaimData(claimIdx) candidate, err := r.contract.ResolveClaimTx(claimIdx)
if err != nil { if err != nil {
return err return err
} }
return r.sendTxAndWait(ctx, txData) return r.sendTxAndWait(ctx, candidate)
} }
func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error { func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error {
var txData []byte var candidate txmgr.TxCandidate
var err error var err error
switch action.Type { switch action.Type {
case types.ActionTypeMove: case types.ActionTypeMove:
if action.IsAttack { if action.IsAttack {
txData, err = r.buildFaultAttackData(action.ParentIdx, action.Value) candidate, err = r.contract.AttackTx(uint64(action.ParentIdx), action.Value)
} else { } else {
txData, err = r.buildFaultDefendData(action.ParentIdx, action.Value) candidate, err = r.contract.DefendTx(uint64(action.ParentIdx), action.Value)
} }
case types.ActionTypeStep: case types.ActionTypeStep:
txData, err = r.buildStepTxData(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData) candidate, err = r.contract.StepTx(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData)
} }
if err != nil { if err != nil {
return err return err
} }
return r.sendTxAndWait(ctx, txData) return r.sendTxAndWait(ctx, candidate)
} }
// sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt. // sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt.
// This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr]. // This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr].
func (r *FaultResponder) sendTxAndWait(ctx context.Context, txData []byte) error { func (r *FaultResponder) sendTxAndWait(ctx context.Context, candidate txmgr.TxCandidate) error {
receipt, err := r.txMgr.Send(ctx, txmgr.TxCandidate{ receipt, err := r.txMgr.Send(ctx, candidate)
To: &r.fdgAddr,
TxData: txData,
GasLimit: 0,
})
if err != nil { if err != nil {
return err return err
} }
...@@ -159,14 +103,3 @@ func (r *FaultResponder) sendTxAndWait(ctx context.Context, txData []byte) error ...@@ -159,14 +103,3 @@ func (r *FaultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
} }
return nil return nil
} }
// buildStepTxData creates the transaction data for the step function.
func (r *FaultResponder) buildStepTxData(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) ([]byte, error) {
return r.fdgAbi.Pack(
"step",
big.NewInt(int64(claimIdx)),
isAttack,
stateData,
proof,
)
}
...@@ -12,7 +12,7 @@ type GameSolver struct { ...@@ -12,7 +12,7 @@ type GameSolver struct {
claimSolver *claimSolver claimSolver *claimSolver
} }
func NewGameSolver(gameDepth int, trace types.TraceProvider) *GameSolver { func NewGameSolver(gameDepth int, trace types.TraceAccessor) *GameSolver {
return &GameSolver{ return &GameSolver{
claimSolver: newClaimSolver(gameDepth, trace), claimSolver: newClaimSolver(gameDepth, trace),
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"testing" "testing"
faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test" faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -113,7 +114,7 @@ func TestCalculateNextActions(t *testing.T) { ...@@ -113,7 +114,7 @@ func TestCalculateNextActions(t *testing.T) {
i, claim.Position.ToGIndex(), claim.Position.TraceIndex(maxDepth), claim.ParentContractIndex, claim.Countered, claim.Value) i, claim.Position.ToGIndex(), claim.Position.TraceIndex(maxDepth), claim.ParentContractIndex, claim.Countered, claim.Value)
} }
solver := NewGameSolver(maxDepth, claimBuilder.CorrectTraceProvider()) solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()))
actions, err := solver.CalculateNextActions(context.Background(), game) actions, err := solver.CalculateNextActions(context.Background(), game)
require.NoError(t, err) require.NoError(t, err)
for i, action := range actions { for i, action := range actions {
......
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
) )
var ( var (
...@@ -18,14 +17,14 @@ var ( ...@@ -18,14 +17,14 @@ var (
// claimSolver uses a [TraceProvider] to determine the moves to make in a dispute game. // claimSolver uses a [TraceProvider] to determine the moves to make in a dispute game.
type claimSolver struct { type claimSolver struct {
trace types.TraceProvider trace types.TraceAccessor
gameDepth int gameDepth int
} }
// newClaimSolver creates a new [claimSolver] using the provided [TraceProvider]. // newClaimSolver creates a new [claimSolver] using the provided [TraceProvider].
func newClaimSolver(gameDepth int, traceProvider types.TraceProvider) *claimSolver { func newClaimSolver(gameDepth int, trace types.TraceAccessor) *claimSolver {
return &claimSolver{ return &claimSolver{
traceProvider, trace,
gameDepth, gameDepth,
} }
} }
...@@ -53,14 +52,14 @@ func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game type ...@@ -53,14 +52,14 @@ func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game type
} }
} }
agree, err := s.agreeWithClaim(ctx, claim.ClaimData) agree, err := s.agreeWithClaim(ctx, game, claim)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if agree { if agree {
return s.defend(ctx, claim) return s.defend(ctx, game, claim)
} else { } else {
return s.attack(ctx, claim) return s.attack(ctx, game, claim)
} }
} }
...@@ -93,28 +92,25 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty ...@@ -93,28 +92,25 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty
return StepData{}, ErrStepIgnoreInvalidPath return StepData{}, ErrStepIgnoreInvalidPath
} }
claimCorrect, err := s.agreeWithClaim(ctx, claim.ClaimData) claimCorrect, err := s.agreeWithClaim(ctx, game, claim)
if err != nil { if err != nil {
return StepData{}, err return StepData{}, err
} }
var preState []byte
var proofData []byte
var oracleData *types.PreimageOracleData
var position types.Position
if !claimCorrect { if !claimCorrect {
// Attack the claim by executing step index, so we need to get the pre-state of that index // Attack the claim by executing step index, so we need to get the pre-state of that index
preState, proofData, oracleData, err = s.trace.GetStepData(ctx, claim.Position) position = claim.Position
if err != nil {
return StepData{}, err
}
} else { } else {
// We agree with the claim so Defend and use this claim as the starting point to // Defend and use this claim as the starting point to execute the step after.
// execute the step after. Thus we need the pre-state of the next step. // Thus, we need the pre-state of the next step.
preState, proofData, oracleData, err = s.trace.GetStepData(ctx, claim.MoveRight()) position = claim.Position.MoveRight()
}
preState, proofData, oracleData, err := s.trace.GetStepData(ctx, game, claim, position)
if err != nil { if err != nil {
return StepData{}, err return StepData{}, err
} }
}
return StepData{ return StepData{
LeafClaim: claim, LeafClaim: claim,
...@@ -126,9 +122,9 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty ...@@ -126,9 +122,9 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty
} }
// attack returns a response that attacks the claim. // attack returns a response that attacks the claim.
func (s *claimSolver) attack(ctx context.Context, claim types.Claim) (*types.Claim, error) { func (s *claimSolver) attack(ctx context.Context, game types.Game, claim types.Claim) (*types.Claim, error) {
position := claim.Attack() position := claim.Attack()
value, err := s.traceAtPosition(ctx, position) value, err := s.trace.Get(ctx, game, claim, position)
if err != nil { if err != nil {
return nil, fmt.Errorf("attack claim: %w", err) return nil, fmt.Errorf("attack claim: %w", err)
} }
...@@ -139,12 +135,12 @@ func (s *claimSolver) attack(ctx context.Context, claim types.Claim) (*types.Cla ...@@ -139,12 +135,12 @@ func (s *claimSolver) attack(ctx context.Context, claim types.Claim) (*types.Cla
} }
// defend returns a response that defends the claim. // defend returns a response that defends the claim.
func (s *claimSolver) defend(ctx context.Context, claim types.Claim) (*types.Claim, error) { func (s *claimSolver) defend(ctx context.Context, game types.Game, claim types.Claim) (*types.Claim, error) {
if claim.IsRoot() { if claim.IsRoot() {
return nil, nil return nil, nil
} }
position := claim.Defend() position := claim.Defend()
value, err := s.traceAtPosition(ctx, position) value, err := s.trace.Get(ctx, game, claim, position)
if err != nil { if err != nil {
return nil, fmt.Errorf("defend claim: %w", err) return nil, fmt.Errorf("defend claim: %w", err)
} }
...@@ -155,19 +151,14 @@ func (s *claimSolver) defend(ctx context.Context, claim types.Claim) (*types.Cla ...@@ -155,19 +151,14 @@ func (s *claimSolver) defend(ctx context.Context, claim types.Claim) (*types.Cla
} }
// agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider]. // agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider].
func (s *claimSolver) agreeWithClaim(ctx context.Context, claim types.ClaimData) (bool, error) { func (s *claimSolver) agreeWithClaim(ctx context.Context, game types.Game, claim types.Claim) (bool, error) {
ourValue, err := s.traceAtPosition(ctx, claim.Position) ourValue, err := s.trace.Get(ctx, game, claim, claim.Position)
return bytes.Equal(ourValue[:], claim.Value[:]), err return bytes.Equal(ourValue[:], claim.Value[:]), err
} }
// traceAtPosition returns the [common.Hash] from internal [TraceProvider] at the given [Position].
func (s *claimSolver) traceAtPosition(ctx context.Context, p types.Position) (common.Hash, error) {
return s.trace.Get(ctx, p)
}
// agreeWithClaimPath returns true if the every other claim in the path to root is correct according to the internal [TraceProvider]. // agreeWithClaimPath returns true if the every other claim in the path to root is correct according to the internal [TraceProvider].
func (s *claimSolver) agreeWithClaimPath(ctx context.Context, game types.Game, claim types.Claim) (bool, error) { func (s *claimSolver) agreeWithClaimPath(ctx context.Context, game types.Game, claim types.Claim) (bool, error) {
agree, err := s.agreeWithClaim(ctx, claim.ClaimData) agree, err := s.agreeWithClaim(ctx, game, claim)
if err != nil { if err != nil {
return false, err return false, err
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"testing" "testing"
faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test" faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -161,7 +162,7 @@ func TestAttemptStep(t *testing.T) { ...@@ -161,7 +162,7 @@ func TestAttemptStep(t *testing.T) {
t.Run(tableTest.name, func(t *testing.T) { t.Run(tableTest.name, func(t *testing.T) {
builder := claimBuilder.GameBuilder(tableTest.agreeWithOutputRoot, !tableTest.agreeWithOutputRoot) builder := claimBuilder.GameBuilder(tableTest.agreeWithOutputRoot, !tableTest.agreeWithOutputRoot)
tableTest.setupGame(builder) tableTest.setupGame(builder)
alphabetSolver := newClaimSolver(maxDepth, claimBuilder.CorrectTraceProvider()) alphabetSolver := newClaimSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()))
game := builder.Game game := builder.Game
claims := game.Claims() claims := game.Claims()
lastClaim := claims[len(claims)-1] lastClaim := claims[len(claims)-1]
......
package trace
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
)
type ProviderCreator func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error)
func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor {
selector := func(_ context.Context, _ types.Game, _ types.Claim, _ types.Position) (types.TraceProvider, error) {
return trace, nil
}
return &Accessor{selector}
}
type Accessor struct {
selector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error)
}
func (t *Accessor) Get(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (common.Hash, error) {
provider, err := t.selector(ctx, game, ref, pos)
if err != nil {
return common.Hash{}, err
}
return provider.Get(ctx, pos)
}
func (t *Accessor) GetStepData(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
provider, err := t.selector(ctx, game, ref, pos)
if err != nil {
return nil, nil, nil, err
}
return provider.GetStepData(ctx, pos)
}
var _ types.TraceAccessor = (*Accessor)(nil)
package trace
import (
"context"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/stretchr/testify/require"
)
func TestAccessor_UsesSelector(t *testing.T) {
ctx := context.Background()
depth := uint64(4)
provider1 := test.NewAlphabetWithProofProvider(t, int(depth), nil)
provider2 := alphabet.NewTraceProvider("qrstuv", depth)
claim := types.Claim{}
game := types.NewGameState(true, []types.Claim{claim}, depth)
pos1 := types.NewPositionFromGIndex(big.NewInt(4))
pos2 := types.NewPositionFromGIndex(big.NewInt(6))
accessor := &Accessor{
selector: func(ctx context.Context, actualGame types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) {
require.Equal(t, game, actualGame)
require.Equal(t, claim, ref)
if pos == pos1 {
return provider1, nil
} else if pos == pos2 {
return provider2, nil
}
return nil, fmt.Errorf("incorrect position requested: %v", pos)
},
}
t.Run("Get", func(t *testing.T) {
actual, err := accessor.Get(ctx, game, claim, pos1)
require.NoError(t, err)
expected, err := provider1.Get(ctx, pos1)
require.NoError(t, err)
require.Equal(t, expected, actual)
actual, err = accessor.Get(ctx, game, claim, pos2)
require.NoError(t, err)
expected, err = provider2.Get(ctx, pos2)
require.NoError(t, err)
require.Equal(t, expected, actual)
})
t.Run("GetStepData", func(t *testing.T) {
actualPrestate, actualProofData, actualPreimageData, err := accessor.GetStepData(ctx, game, claim, pos1)
require.NoError(t, err)
expectedPrestate, expectedProofData, expectedPreimageData, err := provider1.GetStepData(ctx, pos1)
require.NoError(t, err)
require.Equal(t, expectedPrestate, actualPrestate)
require.Equal(t, expectedProofData, actualProofData)
require.Equal(t, expectedPreimageData, actualPreimageData)
actualPrestate, actualProofData, actualPreimageData, err = accessor.GetStepData(ctx, game, claim, pos2)
require.NoError(t, err)
expectedPrestate, expectedProofData, expectedPreimageData, err = provider2.GetStepData(ctx, pos2)
require.NoError(t, err)
require.Equal(t, expectedPrestate, actualPrestate)
require.Equal(t, expectedProofData, actualProofData)
require.Equal(t, expectedPreimageData, actualPreimageData)
})
}
...@@ -5,8 +5,7 @@ import ( ...@@ -5,8 +5,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
) )
...@@ -25,26 +24,20 @@ type L2DataSource interface { ...@@ -25,26 +24,20 @@ type L2DataSource interface {
} }
type GameInputsSource interface { type GameInputsSource interface {
L1Head(opts *bind.CallOpts) ([32]byte, error) GetL1Head(ctx context.Context) (common.Hash, error)
Proposals(opts *bind.CallOpts) (struct { GetProposals(ctx context.Context) (agreed contracts.Proposal, disputed contracts.Proposal, err error)
Starting bindings.IFaultDisputeGameOutputProposal
Disputed bindings.IFaultDisputeGameOutputProposal
}, error)
} }
func fetchLocalInputs(ctx context.Context, gameAddr common.Address, caller GameInputsSource, l2Client L2DataSource) (LocalGameInputs, error) { func fetchLocalInputs(ctx context.Context, caller GameInputsSource, l2Client L2DataSource) (LocalGameInputs, error) {
opts := &bind.CallOpts{Context: ctx} l1Head, err := caller.GetL1Head(ctx)
l1Head, err := caller.L1Head(opts)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch L1 head for game %v: %w", gameAddr, err) return LocalGameInputs{}, fmt.Errorf("fetch L1 head: %w", err)
} }
proposals, err := caller.Proposals(opts) agreedOutput, claimedOutput, err := caller.GetProposals(ctx)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err) return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err)
} }
claimedOutput := proposals.Disputed
agreedOutput := proposals.Starting
agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber) agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch L2 block header %v: %w", agreedOutput.L2BlockNumber, err) return LocalGameInputs{}, fmt.Errorf("fetch L2 block header %v: %w", agreedOutput.L2BlockNumber, err)
......
...@@ -5,9 +5,8 @@ import ( ...@@ -5,9 +5,8 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -15,15 +14,14 @@ import ( ...@@ -15,15 +14,14 @@ import (
func TestFetchLocalInputs(t *testing.T) { func TestFetchLocalInputs(t *testing.T) {
ctx := context.Background() ctx := context.Background()
gameAddr := common.Address{0xab} contract := &mockGameInputsSource{
l1Client := &mockGameInputsSource{
l1Head: common.Hash{0xcc}, l1Head: common.Hash{0xcc},
starting: bindings.IFaultDisputeGameOutputProposal{ starting: contracts.Proposal{
Index: big.NewInt(6), Index: big.NewInt(6),
L2BlockNumber: big.NewInt(2222), L2BlockNumber: big.NewInt(2222),
OutputRoot: common.Hash{0xdd}, OutputRoot: common.Hash{0xdd},
}, },
disputed: bindings.IFaultDisputeGameOutputProposal{ disputed: contracts.Proposal{
Index: big.NewInt(7), Index: big.NewInt(7),
L2BlockNumber: big.NewInt(3333), L2BlockNumber: big.NewInt(3333),
OutputRoot: common.Hash{0xee}, OutputRoot: common.Hash{0xee},
...@@ -32,41 +30,32 @@ func TestFetchLocalInputs(t *testing.T) { ...@@ -32,41 +30,32 @@ func TestFetchLocalInputs(t *testing.T) {
l2Client := &mockL2DataSource{ l2Client := &mockL2DataSource{
chainID: big.NewInt(88422), chainID: big.NewInt(88422),
header: ethtypes.Header{ header: ethtypes.Header{
Number: l1Client.starting.L2BlockNumber, Number: contract.starting.L2BlockNumber,
}, },
} }
inputs, err := fetchLocalInputs(ctx, gameAddr, l1Client, l2Client) inputs, err := fetchLocalInputs(ctx, contract, l2Client)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Client.l1Head, inputs.L1Head) require.Equal(t, contract.l1Head, inputs.L1Head)
require.Equal(t, l2Client.header.Hash(), inputs.L2Head) require.Equal(t, l2Client.header.Hash(), inputs.L2Head)
require.EqualValues(t, l1Client.starting.OutputRoot, inputs.L2OutputRoot) require.EqualValues(t, contract.starting.OutputRoot, inputs.L2OutputRoot)
require.EqualValues(t, l1Client.disputed.OutputRoot, inputs.L2Claim) require.EqualValues(t, contract.disputed.OutputRoot, inputs.L2Claim)
require.Equal(t, l1Client.disputed.L2BlockNumber, inputs.L2BlockNumber) require.Equal(t, contract.disputed.L2BlockNumber, inputs.L2BlockNumber)
} }
type mockGameInputsSource struct { type mockGameInputsSource struct {
l1Head common.Hash l1Head common.Hash
starting bindings.IFaultDisputeGameOutputProposal starting contracts.Proposal
disputed bindings.IFaultDisputeGameOutputProposal disputed contracts.Proposal
} }
func (s *mockGameInputsSource) L1Head(opts *bind.CallOpts) ([32]byte, error) { func (s *mockGameInputsSource) GetL1Head(_ context.Context) (common.Hash, error) {
return s.l1Head, nil return s.l1Head, nil
} }
func (s *mockGameInputsSource) Proposals(opts *bind.CallOpts) (struct { func (s *mockGameInputsSource) GetProposals(_ context.Context) (contracts.Proposal, contracts.Proposal, error) {
Starting bindings.IFaultDisputeGameOutputProposal return s.starting, s.disputed, nil
Disputed bindings.IFaultDisputeGameOutputProposal
}, error) {
return struct {
Starting bindings.IFaultDisputeGameOutputProposal
Disputed bindings.IFaultDisputeGameOutputProposal
}{
Starting: s.starting,
Disputed: s.disputed,
}, nil
} }
type mockL2DataSource struct { type mockL2DataSource struct {
......
...@@ -8,11 +8,10 @@ import ( ...@@ -8,11 +8,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/ethclient" "github.com/ethereum/go-ethereum/ethclient"
...@@ -56,17 +55,13 @@ type CannonTraceProvider struct { ...@@ -56,17 +55,13 @@ type CannonTraceProvider struct {
lastStep uint64 lastStep uint64
} }
func NewTraceProvider(ctx context.Context, logger log.Logger, m CannonMetricer, cfg *config.Config, l1Client bind.ContractCaller, dir string, gameAddr common.Address, gameDepth uint64) (*CannonTraceProvider, error) { func NewTraceProvider(ctx context.Context, logger log.Logger, m CannonMetricer, cfg *config.Config, gameContract *contracts.FaultDisputeGameContract, dir string, gameDepth uint64) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2) l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil { if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err) return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
} }
defer l2Client.Close() // Not needed after fetching the inputs defer l2Client.Close() // Not needed after fetching the inputs
gameCaller, err := bindings.NewFaultDisputeGameCaller(gameAddr, l1Client) localInputs, err := fetchLocalInputs(ctx, gameContract, l2Client)
if err != nil {
return nil, fmt.Errorf("create caller for game %v: %w", gameAddr, err)
}
localInputs, err := fetchLocalInputs(ctx, gameAddr, gameCaller, l2Client)
if err != nil { if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err) return nil, fmt.Errorf("fetch local game inputs: %w", err)
} }
......
...@@ -3,7 +3,6 @@ package cannon ...@@ -3,7 +3,6 @@ package cannon
import ( import (
"context" "context"
"errors" "errors"
"math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
...@@ -11,7 +10,6 @@ import ( ...@@ -11,7 +10,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -47,10 +45,6 @@ func (m *mockTxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) ( ...@@ -47,10 +45,6 @@ func (m *mockTxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) (
), nil ), nil
} }
func (m *mockTxManager) Call(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) {
panic("not implemented")
}
func (m *mockTxManager) BlockNumber(ctx context.Context) (uint64, error) { func (m *mockTxManager) BlockNumber(ctx context.Context) (uint64, error) {
panic("not implemented") panic("not implemented")
} }
......
...@@ -57,6 +57,18 @@ type OracleUpdater interface { ...@@ -57,6 +57,18 @@ type OracleUpdater interface {
UpdateOracle(ctx context.Context, data *PreimageOracleData) error UpdateOracle(ctx context.Context, data *PreimageOracleData) error
} }
// TraceAccessor defines an interface to request data from a TraceProvider with additional context for the game position.
// This can be used to implement split games where lower layers of the game may have different values depending on claims
// at higher levels in the game.
type TraceAccessor interface {
// Get returns the claim value at the requested position, evaluated in the context of the specified claim (ref).
Get(ctx context.Context, game Game, ref Claim, pos Position) (common.Hash, error)
// GetStepData returns the data required to execute the step at the specified position,
// evaluated in the context of the specified claim (ref).
GetStepData(ctx context.Context, game Game, ref Claim, pos Position) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error)
}
// TraceProvider is a generic way to get a claim value at a specific step in the trace. // TraceProvider is a generic way to get a claim value at a specific step in the trace.
type TraceProvider interface { type TraceProvider interface {
// Get returns the claim value at the requested index. // Get returns the claim value at the requested index.
......
...@@ -23,10 +23,6 @@ test-http: pre-test ...@@ -23,10 +23,6 @@ test-http: pre-test
OP_E2E_USE_HTTP=true $(go_test) $(go_test_flags) ./... OP_E2E_USE_HTTP=true $(go_test) $(go_test_flags) ./...
.PHONY: test-http .PHONY: test-http
test-span-batch: pre-test
OP_E2E_USE_SPAN_BATCH=true $(go_test) $(go_test_flags) ./...
.PHONY: test-span-batch
cannon-prestate: cannon-prestate:
make -C .. cannon-prestate make -C .. cannon-prestate
.PHONY: cannon-prestate .PHONY: cannon-prestate
......
...@@ -6,20 +6,47 @@ import ( ...@@ -6,20 +6,47 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"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/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// TestBatchInLastPossibleBlocks tests that the derivation pipeline // TestBlockTimeBatchType run each blocktime-related test case in singular batch mode and span batch mode.
func TestBlockTimeBatchType(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64)
}{
{"BatchInLastPossibleBlocks", BatchInLastPossibleBlocks},
{"LargeL1Gaps", LargeL1Gaps},
}
for _, test := range tests {
test := test
t.Run(test.name+"_SingularBatch", func(t *testing.T) {
test.f(t, nil)
})
}
spanBatchTimeOffset := hexutil.Uint64(0)
for _, test := range tests {
test := test
t.Run(test.name+"_SpanBatch", func(t *testing.T) {
test.f(t, &spanBatchTimeOffset)
})
}
}
// BatchInLastPossibleBlocks tests that the derivation pipeline
// accepts a batch that is included in the last possible L1 block // accepts a batch that is included in the last possible L1 block
// where there are also no other batches included in the sequence // where there are also no other batches included in the sequence
// window. // window.
// This is a regression test against the bug fixed in PR #4566 // This is a regression test against the bug fixed in PR #4566
func TestBatchInLastPossibleBlocks(gt *testing.T) { func BatchInLastPossibleBlocks(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
dp.DeployConfig.SequencerWindowSize = 4 dp.DeployConfig.SequencerWindowSize = 4
dp.DeployConfig.L2BlockTime = 2 dp.DeployConfig.L2BlockTime = 2
...@@ -116,7 +143,7 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) { ...@@ -116,7 +143,7 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) {
verifyChainStateOnSequencer(12, 23, 11, 17, 8) verifyChainStateOnSequencer(12, 23, 11, 17, 8)
} }
// TestLargeL1Gaps tests the case that there is a gap between two L1 blocks which // LargeL1Gaps tests the case that there is a gap between two L1 blocks which
// is larger than the sequencer drift. // is larger than the sequencer drift.
// This test has the following parameters: // This test has the following parameters:
// L1 Block time: 4s. L2 Block time: 2s. Sequencer Drift: 32s // L1 Block time: 4s. L2 Block time: 2s. Sequencer Drift: 32s
...@@ -127,13 +154,14 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) { ...@@ -127,13 +154,14 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) {
// Then it generates 3 more L1 blocks. // Then it generates 3 more L1 blocks.
// At this point it can verify that the batches where properly generated. // At this point it can verify that the batches where properly generated.
// Note: It batches submits when possible. // Note: It batches submits when possible.
func TestLargeL1Gaps(gt *testing.T) { func LargeL1Gaps(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L1BlockTime = 4 dp.DeployConfig.L1BlockTime = 4
dp.DeployConfig.L2BlockTime = 2 dp.DeployConfig.L2BlockTime = 2
dp.DeployConfig.SequencerWindowSize = 4 dp.DeployConfig.SequencerWindowSize = 4
dp.DeployConfig.MaxSequencerDrift = 32 dp.DeployConfig.MaxSequencerDrift = 32
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
......
package actions
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)
func TestDencunL1Fork(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
activation := sd.L1Cfg.Timestamp + 24
sd.L1Cfg.Config.CancunTime = &activation
log := testlog.Logger(t, log.LvlDebug)
_, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)
l1Head := miner.l1Chain.CurrentBlock()
require.False(t, sd.L1Cfg.Config.IsCancun(l1Head.Number, l1Head.Time), "Cancun not active yet")
// start op-nodes
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
// build empty L1 blocks, crossing the fork boundary
miner.ActL1SetFeeRecipient(common.Address{'A', 0})
miner.ActEmptyBlock(t)
miner.ActEmptyBlock(t) // Cancun activates here
miner.ActEmptyBlock(t)
// verify Cancun is active
l1Head = miner.l1Chain.CurrentBlock()
require.True(t, sd.L1Cfg.Config.IsCancun(l1Head.Number, l1Head.Time), "Cancun active")
// build L2 chain up to and including L2 blocks referencing Cancun L1 blocks
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1Head(t)
miner.ActL1StartBlock(12)(t)
batcher.ActSubmitAll(t)
miner.ActL1IncludeTx(batcher.batcherAddr)(t)
miner.ActL1EndBlock(t)
// sync verifier
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
// verify verifier accepted Cancun L1 inputs
require.Equal(t, l1Head.Hash(), verifier.SyncStatus().SafeL2.L1Origin.Hash, "verifier synced L1 chain that includes Cancun headers")
require.Equal(t, sequencer.SyncStatus().UnsafeL2, verifier.SyncStatus().UnsafeL2, "verifier and sequencer agree")
}
...@@ -76,6 +76,13 @@ func (s *L1Miner) ActL1StartBlock(timeDelta uint64) Action { ...@@ -76,6 +76,13 @@ func (s *L1Miner) ActL1StartBlock(timeDelta uint64) Action {
if s.l1Cfg.Config.IsShanghai(header.Number, header.Time) { if s.l1Cfg.Config.IsShanghai(header.Number, header.Time) {
header.WithdrawalsHash = &types.EmptyWithdrawalsHash header.WithdrawalsHash = &types.EmptyWithdrawalsHash
} }
if s.l1Cfg.Config.IsCancun(header.Number, header.Time) {
var root common.Hash
var zero uint64
header.BlobGasUsed = &zero
header.ExcessBlobGas = &zero
header.ParentBeaconRoot = &root
}
s.l1Building = true s.l1Building = true
s.l1BuildingHeader = header s.l1BuildingHeader = header
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -20,7 +21,36 @@ import ( ...@@ -20,7 +21,36 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
func TestBatcher(gt *testing.T) { // TestL2BatcherBatchType run each batcher-related test case in singular batch mode and span batch mode.
func TestL2BatcherBatchType(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64)
}{
{"NormalBatcher", NormalBatcher},
{"L2Finalization", L2Finalization},
{"L2FinalizationWithSparseL1", L2FinalizationWithSparseL1},
{"GarbageBatch", GarbageBatch},
{"ExtendedTimeWithoutL1Batches", ExtendedTimeWithoutL1Batches},
{"BigL2Txs", BigL2Txs},
}
for _, test := range tests {
test := test
t.Run(test.name+"_SingularBatch", func(t *testing.T) {
test.f(t, nil)
})
}
spanBatchTimeOffset := hexutil.Uint64(0)
for _, test := range tests {
test := test
t.Run(test.name+"_SpanBatch", func(t *testing.T) {
test.f(t, &spanBatchTimeOffset)
})
}
}
func NormalBatcher(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{ p := &e2eutils.TestParams{
MaxSequencerDrift: 20, // larger than L1 block time we simulate in this test (12) MaxSequencerDrift: 20, // larger than L1 block time we simulate in this test (12)
...@@ -29,6 +59,7 @@ func TestBatcher(gt *testing.T) { ...@@ -29,6 +59,7 @@ func TestBatcher(gt *testing.T) {
L1BlockTime: 12, L1BlockTime: 12,
} }
dp := e2eutils.MakeDeployParams(t, p) dp := e2eutils.MakeDeployParams(t, p)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
...@@ -55,7 +86,7 @@ func TestBatcher(gt *testing.T) { ...@@ -55,7 +86,7 @@ func TestBatcher(gt *testing.T) {
To: &dp.Addresses.Bob, To: &dp.Addresses.Bob,
Value: e2eutils.Ether(2), Value: e2eutils.Ether(2),
}) })
require.NoError(gt, cl.SendTransaction(t.Ctx(), tx)) require.NoError(t, cl.SendTransaction(t.Ctx(), tx))
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
...@@ -97,9 +128,10 @@ func TestBatcher(gt *testing.T) { ...@@ -97,9 +128,10 @@ func TestBatcher(gt *testing.T) {
require.NotNil(t, vTx) require.NotNil(t, vTx)
} }
func TestL2Finalization(gt *testing.T) { func L2Finalization(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, engine, sequencer := setupSequencerTest(t, sd, log) miner, engine, sequencer := setupSequencerTest(t, sd, log)
...@@ -202,10 +234,11 @@ func TestL2Finalization(gt *testing.T) { ...@@ -202,10 +234,11 @@ func TestL2Finalization(gt *testing.T) {
require.Equal(t, heightToSubmit, sequencer.SyncStatus().FinalizedL2.Number, "unknown/bad finalized L1 blocks are ignored") require.Equal(t, heightToSubmit, sequencer.SyncStatus().FinalizedL2.Number, "unknown/bad finalized L1 blocks are ignored")
} }
// TestL2FinalizationWithSparseL1 tests that safe L2 blocks can be finalized even if we do not regularly get a L1 finalization signal // L2FinalizationWithSparseL1 tests that safe L2 blocks can be finalized even if we do not regularly get a L1 finalization signal
func TestL2FinalizationWithSparseL1(gt *testing.T) { func L2FinalizationWithSparseL1(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, engine, sequencer := setupSequencerTest(t, sd, log) miner, engine, sequencer := setupSequencerTest(t, sd, log)
...@@ -258,13 +291,14 @@ func TestL2FinalizationWithSparseL1(gt *testing.T) { ...@@ -258,13 +291,14 @@ func TestL2FinalizationWithSparseL1(gt *testing.T) {
require.Equal(t, finalStatus.FinalizedL2.Number, finalStatus.UnsafeL2.Number, "sequencer submitted its L2 block and it finalized") require.Equal(t, finalStatus.FinalizedL2.Number, finalStatus.UnsafeL2.Number, "sequencer submitted its L2 block and it finalized")
} }
// TestGarbageBatch tests the behavior of an invalid/malformed output channel frame containing // GarbageBatch tests the behavior of an invalid/malformed output channel frame containing
// valid batches being submitted to the batch inbox. These batches should always be rejected // valid batches being submitted to the batch inbox. These batches should always be rejected
// and the safe L2 head should remain unaltered. // and the safe L2 head should remain unaltered.
func TestGarbageBatch(gt *testing.T) { func GarbageBatch(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
p := defaultRollupTestParams p := defaultRollupTestParams
dp := e2eutils.MakeDeployParams(t, p) dp := e2eutils.MakeDeployParams(t, p)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
for _, garbageKind := range GarbageKinds { for _, garbageKind := range GarbageKinds {
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError) log := testlog.Logger(t, log.LvlError)
...@@ -340,7 +374,7 @@ func TestGarbageBatch(gt *testing.T) { ...@@ -340,7 +374,7 @@ func TestGarbageBatch(gt *testing.T) {
} }
} }
func TestExtendedTimeWithoutL1Batches(gt *testing.T) { func ExtendedTimeWithoutL1Batches(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{ p := &e2eutils.TestParams{
MaxSequencerDrift: 20, // larger than L1 block time we simulate in this test (12) MaxSequencerDrift: 20, // larger than L1 block time we simulate in this test (12)
...@@ -349,6 +383,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { ...@@ -349,6 +383,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
L1BlockTime: 12, L1BlockTime: 12,
} }
dp := e2eutils.MakeDeployParams(t, p) dp := e2eutils.MakeDeployParams(t, p)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError) log := testlog.Logger(t, log.LvlError)
miner, engine, sequencer := setupSequencerTest(t, sd, log) miner, engine, sequencer := setupSequencerTest(t, sd, log)
...@@ -386,7 +421,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { ...@@ -386,7 +421,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
require.Equal(t, sequencer.L2Unsafe(), sequencer.L2Safe(), "same for sequencer") require.Equal(t, sequencer.L2Unsafe(), sequencer.L2Safe(), "same for sequencer")
} }
// TestBigL2Txs tests a high-throughput case with constrained batcher: // BigL2Txs tests a high-throughput case with constrained batcher:
// - Fill 40 L2 blocks to near max-capacity, with txs of 120 KB each // - Fill 40 L2 blocks to near max-capacity, with txs of 120 KB each
// - Buffer the L2 blocks into channels together as much as possible, submit data-txs only when necessary // - Buffer the L2 blocks into channels together as much as possible, submit data-txs only when necessary
// (just before crossing the max RLP channel size) // (just before crossing the max RLP channel size)
...@@ -398,7 +433,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { ...@@ -398,7 +433,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
// The goal of this test is to quickly run through an otherwise very slow process of submitting and including lots of data. // The goal of this test is to quickly run through an otherwise very slow process of submitting and including lots of data.
// This does not test the batcher code, but is really focused at testing the batcher utils // This does not test the batcher code, but is really focused at testing the batcher utils
// and channel-decoding verifier code in the derive package. // and channel-decoding verifier code in the derive package.
func TestBigL2Txs(gt *testing.T) { func BigL2Txs(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{ p := &e2eutils.TestParams{
MaxSequencerDrift: 100, MaxSequencerDrift: 100,
...@@ -407,6 +442,7 @@ func TestBigL2Txs(gt *testing.T) { ...@@ -407,6 +442,7 @@ func TestBigL2Txs(gt *testing.T) {
L1BlockTime: 12, L1BlockTime: 12,
} }
dp := e2eutils.MakeDeployParams(t, p) dp := e2eutils.MakeDeployParams(t, p)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlInfo) log := testlog.Logger(t, log.LvlInfo)
miner, engine, sequencer := setupSequencerTest(t, sd, log) miner, engine, sequencer := setupSequencerTest(t, sd, log)
...@@ -464,7 +500,7 @@ func TestBigL2Txs(gt *testing.T) { ...@@ -464,7 +500,7 @@ func TestBigL2Txs(gt *testing.T) {
Value: big.NewInt(0), Value: big.NewInt(0),
Data: data, Data: data,
}) })
require.NoError(gt, cl.SendTransaction(t.Ctx(), tx)) require.NoError(t, cl.SendTransaction(t.Ctx(), tx))
engine.ActL2IncludeTx(dp.Addresses.Alice)(t) engine.ActL2IncludeTx(dp.Addresses.Alice)(t)
} }
sequencer.ActL2EndBlock(t) sequencer.ActL2EndBlock(t)
......
...@@ -48,9 +48,6 @@ type fakeTxMgr struct { ...@@ -48,9 +48,6 @@ type fakeTxMgr struct {
func (f fakeTxMgr) From() common.Address { func (f fakeTxMgr) From() common.Address {
return f.from return f.from
} }
func (f fakeTxMgr) Call(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) {
panic("unimplemented")
}
func (f fakeTxMgr) BlockNumber(_ context.Context) (uint64, error) { func (f fakeTxMgr) BlockNumber(_ context.Context) (uint64, error) {
panic("unimplemented") panic("unimplemented")
} }
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -14,9 +15,34 @@ import ( ...@@ -14,9 +15,34 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
func TestProposer(gt *testing.T) { // TestProposerBatchType run each proposer-related test case in singular batch mode and span batch mode.
func TestProposerBatchType(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64)
}{
{"RunProposerTest", RunProposerTest},
}
for _, test := range tests {
test := test
t.Run(test.name+"_SingularBatch", func(t *testing.T) {
test.f(t, nil)
})
}
spanBatchTimeOffset := hexutil.Uint64(0)
for _, test := range tests {
test := test
t.Run(test.name+"_SpanBatch", func(t *testing.T) {
test.f(t, &spanBatchTimeOffset)
})
}
}
func RunProposerTest(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -21,8 +22,9 @@ import ( ...@@ -21,8 +22,9 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
func setupReorgTest(t Testing, config *e2eutils.TestParams) (*e2eutils.SetupData, *e2eutils.DeployParams, *L1Miner, *L2Sequencer, *L2Engine, *L2Verifier, *L2Engine, *L2Batcher) { func setupReorgTest(t Testing, config *e2eutils.TestParams, spanBatchTimeOffset *hexutil.Uint64) (*e2eutils.SetupData, *e2eutils.DeployParams, *L1Miner, *L2Sequencer, *L2Engine, *L2Verifier, *L2Engine, *L2Batcher) {
dp := e2eutils.MakeDeployParams(t, config) dp := e2eutils.MakeDeployParams(t, config)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
...@@ -44,9 +46,38 @@ func setupReorgTestActors(t Testing, dp *e2eutils.DeployParams, sd *e2eutils.Set ...@@ -44,9 +46,38 @@ func setupReorgTestActors(t Testing, dp *e2eutils.DeployParams, sd *e2eutils.Set
return sd, dp, miner, sequencer, seqEngine, verifier, verifEngine, batcher return sd, dp, miner, sequencer, seqEngine, verifier, verifEngine, batcher
} }
func TestReorgOrphanBlock(gt *testing.T) { // TestReorgBatchType run each reorg-related test case in singular batch mode and span batch mode.
func TestReorgBatchType(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64)
}{
{"ReorgOrphanBlock", ReorgOrphanBlock},
{"ReorgFlipFlop", ReorgFlipFlop},
{"DeepReorg", DeepReorg},
{"RestartOpGeth", RestartOpGeth},
{"ConflictingL2Blocks", ConflictingL2Blocks},
{"SyncAfterReorg", SyncAfterReorg},
}
for _, test := range tests {
test := test
t.Run(test.name+"_SingularBatch", func(t *testing.T) {
test.f(t, nil)
})
}
spanBatchTimeOffset := hexutil.Uint64(0)
for _, test := range tests {
test := test
t.Run(test.name+"_SpanBatch", func(t *testing.T) {
test.f(t, &spanBatchTimeOffset)
})
}
}
func ReorgOrphanBlock(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
sd, _, miner, sequencer, _, verifier, verifierEng, batcher := setupReorgTest(t, defaultRollupTestParams) sd, _, miner, sequencer, _, verifier, verifierEng, batcher := setupReorgTest(t, defaultRollupTestParams, spanBatchTimeOffset)
verifEngClient := verifierEng.EngineClient(t, sd.RollupCfg) verifEngClient := verifierEng.EngineClient(t, sd.RollupCfg)
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
...@@ -112,9 +143,9 @@ func TestReorgOrphanBlock(gt *testing.T) { ...@@ -112,9 +143,9 @@ func TestReorgOrphanBlock(gt *testing.T) {
require.Equal(t, verifier.L2Safe(), sequencer.L2Safe(), "verifier and sequencer see same safe L2 block, while only verifier dealt with the orphan and replay") require.Equal(t, verifier.L2Safe(), sequencer.L2Safe(), "verifier and sequencer see same safe L2 block, while only verifier dealt with the orphan and replay")
} }
func TestReorgFlipFlop(gt *testing.T) { func ReorgFlipFlop(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
sd, _, miner, sequencer, _, verifier, verifierEng, batcher := setupReorgTest(t, defaultRollupTestParams) sd, _, miner, sequencer, _, verifier, verifierEng, batcher := setupReorgTest(t, defaultRollupTestParams, spanBatchTimeOffset)
minerCl := miner.L1Client(t, sd.RollupCfg) minerCl := miner.L1Client(t, sd.RollupCfg)
verifEngClient := verifierEng.EngineClient(t, sd.RollupCfg) verifEngClient := verifierEng.EngineClient(t, sd.RollupCfg)
checkVerifEngine := func() { checkVerifEngine := func() {
...@@ -333,7 +364,7 @@ func TestReorgFlipFlop(gt *testing.T) { ...@@ -333,7 +364,7 @@ func TestReorgFlipFlop(gt *testing.T) {
// Verifier // Verifier
// - Unsafe head is 62 // - Unsafe head is 62
// - Safe head is 42 // - Safe head is 42
func TestDeepReorg(gt *testing.T) { func DeepReorg(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
// Create actor and verification engine client // Create actor and verification engine client
...@@ -342,7 +373,7 @@ func TestDeepReorg(gt *testing.T) { ...@@ -342,7 +373,7 @@ func TestDeepReorg(gt *testing.T) {
SequencerWindowSize: 20, SequencerWindowSize: 20,
ChannelTimeout: 120, ChannelTimeout: 120,
L1BlockTime: 4, L1BlockTime: 4,
}) }, spanBatchTimeOffset)
minerCl := miner.L1Client(t, sd.RollupCfg) minerCl := miner.L1Client(t, sd.RollupCfg)
l2Client := seqEngine.EthClient() l2Client := seqEngine.EthClient()
verifEngClient := verifierEng.EngineClient(t, sd.RollupCfg) verifEngClient := verifierEng.EngineClient(t, sd.RollupCfg)
...@@ -566,9 +597,9 @@ type rpcWrapper struct { ...@@ -566,9 +597,9 @@ type rpcWrapper struct {
client.RPC client.RPC
} }
// TestRestartOpGeth tests that the sequencer can restart its execution engine without rollup-node restart, // RestartOpGeth tests that the sequencer can restart its execution engine without rollup-node restart,
// including recovering the finalized/safe state of L2 chain without reorging. // including recovering the finalized/safe state of L2 chain without reorging.
func TestRestartOpGeth(gt *testing.T) { func RestartOpGeth(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dbPath := path.Join(t.TempDir(), "testdb") dbPath := path.Join(t.TempDir(), "testdb")
dbOption := func(_ *ethconfig.Config, nodeCfg *node.Config) error { dbOption := func(_ *ethconfig.Config, nodeCfg *node.Config) error {
...@@ -576,6 +607,7 @@ func TestRestartOpGeth(gt *testing.T) { ...@@ -576,6 +607,7 @@ func TestRestartOpGeth(gt *testing.T) {
return nil return nil
} }
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
jwtPath := e2eutils.WriteDefaultJWT(t) jwtPath := e2eutils.WriteDefaultJWT(t)
...@@ -660,12 +692,13 @@ func TestRestartOpGeth(gt *testing.T) { ...@@ -660,12 +692,13 @@ func TestRestartOpGeth(gt *testing.T) {
require.Equal(t, statusBeforeRestart.SafeL2, sequencer.L2Safe(), "expecting the safe block to catch up to what it was before shutdown after syncing from L1, and not be stuck at the finalized block") require.Equal(t, statusBeforeRestart.SafeL2, sequencer.L2Safe(), "expecting the safe block to catch up to what it was before shutdown after syncing from L1, and not be stuck at the finalized block")
} }
// TestConflictingL2Blocks tests that a second copy of the sequencer stack cannot introduce an alternative // ConflictingL2Blocks tests that a second copy of the sequencer stack cannot introduce an alternative
// L2 block (compared to something already secured by the first sequencer): // L2 block (compared to something already secured by the first sequencer):
// the alt block is not synced by the verifier, in unsafe and safe sync modes. // the alt block is not synced by the verifier, in unsafe and safe sync modes.
func TestConflictingL2Blocks(gt *testing.T) { func ConflictingL2Blocks(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
...@@ -772,7 +805,7 @@ func TestConflictingL2Blocks(gt *testing.T) { ...@@ -772,7 +805,7 @@ func TestConflictingL2Blocks(gt *testing.T) {
require.Equal(t, sequencer.L2Unsafe(), altSequencer.L2Unsafe(), "and gets back in harmony with original sequencer") require.Equal(t, sequencer.L2Unsafe(), altSequencer.L2Unsafe(), "and gets back in harmony with original sequencer")
} }
func TestSyncAfterReorg(gt *testing.T) { func SyncAfterReorg(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
testingParams := e2eutils.TestParams{ testingParams := e2eutils.TestParams{
MaxSequencerDrift: 60, MaxSequencerDrift: 60,
...@@ -780,7 +813,7 @@ func TestSyncAfterReorg(gt *testing.T) { ...@@ -780,7 +813,7 @@ func TestSyncAfterReorg(gt *testing.T) {
ChannelTimeout: 2, ChannelTimeout: 2,
L1BlockTime: 12, L1BlockTime: 12,
} }
sd, dp, miner, sequencer, seqEngine, verifier, _, batcher := setupReorgTest(t, &testingParams) sd, dp, miner, sequencer, seqEngine, verifier, _, batcher := setupReorgTest(t, &testingParams, spanBatchTimeOffset)
l2Client := seqEngine.EthClient() l2Client := seqEngine.EthClient()
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
addresses := e2eutils.CollectAddresses(sd, dp) addresses := e2eutils.CollectAddresses(sd, dp)
......
...@@ -23,9 +23,35 @@ import ( ...@@ -23,9 +23,35 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestDerivationWithFlakyL1RPC(gt *testing.T) { // TestSyncBatchType run each sync test case in singular batch mode and span batch mode.
func TestSyncBatchType(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64)
}{
{"DerivationWithFlakyL1RPC", DerivationWithFlakyL1RPC},
{"FinalizeWhileSyncing", FinalizeWhileSyncing},
}
for _, test := range tests {
test := test
t.Run(test.name+"_SingularBatch", func(t *testing.T) {
test.f(t, nil)
})
}
spanBatchTimeOffset := hexutil.Uint64(0)
for _, test := range tests {
test := test
t.Run(test.name+"_SpanBatch", func(t *testing.T) {
test.f(t, &spanBatchTimeOffset)
})
}
}
func DerivationWithFlakyL1RPC(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError) // mute all the temporary derivation errors that we forcefully create log := testlog.Logger(t, log.LvlError) // mute all the temporary derivation errors that we forcefully create
_, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log) _, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)
...@@ -62,9 +88,10 @@ func TestDerivationWithFlakyL1RPC(gt *testing.T) { ...@@ -62,9 +88,10 @@ func TestDerivationWithFlakyL1RPC(gt *testing.T) {
require.Equal(t, sequencer.L2Unsafe(), verifier.L2Safe(), "verifier is synced") require.Equal(t, sequencer.L2Unsafe(), verifier.L2Safe(), "verifier is synced")
} }
func TestFinalizeWhileSyncing(gt *testing.T) { func FinalizeWhileSyncing(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError) // mute all the temporary derivation errors that we forcefully create log := testlog.Logger(t, log.LvlError) // mute all the temporary derivation errors that we forcefully create
_, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log) _, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -19,13 +20,40 @@ import ( ...@@ -19,13 +20,40 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
// TestBatcherKeyRotation tests that batcher A can operate, then be replaced with batcher B, then ignore old batcher A, // TestSystemConfigBatchType run each system config-related test case in singular batch mode and span batch mode.
func TestSystemConfigBatchType(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64)
}{
{"BatcherKeyRotation", BatcherKeyRotation},
{"GPOParamsChange", GPOParamsChange},
{"GasLimitChange", GasLimitChange},
}
for _, test := range tests {
test := test
t.Run(test.name+"_SingularBatch", func(t *testing.T) {
test.f(t, nil)
})
}
spanBatchTimeOffset := hexutil.Uint64(0)
for _, test := range tests {
test := test
t.Run(test.name+"_SpanBatch", func(t *testing.T) {
test.f(t, &spanBatchTimeOffset)
})
}
}
// BatcherKeyRotation tests that batcher A can operate, then be replaced with batcher B, then ignore old batcher A,
// and that the change to batcher B is reverted properly upon reorg of L1. // and that the change to batcher B is reverted properly upon reorg of L1.
func TestBatcherKeyRotation(gt *testing.T) { func BatcherKeyRotation(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2BlockTime = 2 dp.DeployConfig.L2BlockTime = 2
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
...@@ -198,11 +226,12 @@ func TestBatcherKeyRotation(gt *testing.T) { ...@@ -198,11 +226,12 @@ func TestBatcherKeyRotation(gt *testing.T) {
require.Equal(t, sequencer.L2Unsafe(), verifier.L2Unsafe(), "verifier synced") require.Equal(t, sequencer.L2Unsafe(), verifier.L2Unsafe(), "verifier synced")
} }
// TestGPOParamsChange tests that the GPO params can be updated to adjust fees of L2 transactions, // GPOParamsChange tests that the GPO params can be updated to adjust fees of L2 transactions,
// and that the L1 data fees to the L2 transaction are applied correctly before, during and after the GPO update in L2. // and that the L1 data fees to the L2 transaction are applied correctly before, during and after the GPO update in L2.
func TestGPOParamsChange(gt *testing.T) { func GPOParamsChange(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
...@@ -326,12 +355,13 @@ func TestGPOParamsChange(gt *testing.T) { ...@@ -326,12 +355,13 @@ func TestGPOParamsChange(gt *testing.T) {
require.Equal(t, "2.3", receipt.FeeScalar.String(), "2_300_000 divided by 6 decimals = float(2.3)") require.Equal(t, "2.3", receipt.FeeScalar.String(), "2_300_000 divided by 6 decimals = float(2.3)")
} }
// TestGasLimitChange tests that the gas limit can be configured to L1, // GasLimitChange tests that the gas limit can be configured to L1,
// and that the L2 changes the gas limit instantly at the exact block that adopts the L1 origin with // and that the L2 changes the gas limit instantly at the exact block that adopts the L1 origin with
// the gas limit change event. And checks if a verifier node can reproduce the same gas limit change. // the gas limit change event. And checks if a verifier node can reproduce the same gas limit change.
func TestGasLimitChange(gt *testing.T) { func GasLimitChange(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
......
...@@ -13,10 +13,12 @@ import ( ...@@ -13,10 +13,12 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
type regolithScheduledTest struct { type hardforkScheduledTest struct {
name string name string
regolithTime *hexutil.Uint64 regolithTime *hexutil.Uint64
spanBatchTime *hexutil.Uint64
activateRegolith bool activateRegolith bool
activateSpanBatch bool
} }
// TestCrossLayerUser tests that common actions of the CrossLayerUser actor work in various regolith configurations: // TestCrossLayerUser tests that common actions of the CrossLayerUser actor work in various regolith configurations:
...@@ -31,11 +33,18 @@ func TestCrossLayerUser(t *testing.T) { ...@@ -31,11 +33,18 @@ func TestCrossLayerUser(t *testing.T) {
zeroTime := hexutil.Uint64(0) zeroTime := hexutil.Uint64(0)
futureTime := hexutil.Uint64(20) futureTime := hexutil.Uint64(20)
farFutureTime := hexutil.Uint64(2000) farFutureTime := hexutil.Uint64(2000)
tests := []regolithScheduledTest{ tests := []hardforkScheduledTest{
{name: "NoRegolith", regolithTime: nil, activateRegolith: false}, {name: "NoRegolith", regolithTime: nil, activateRegolith: false, spanBatchTime: nil, activateSpanBatch: false},
{name: "NotYetRegolith", regolithTime: &farFutureTime, activateRegolith: false}, {name: "NotYetRegolith", regolithTime: &farFutureTime, activateRegolith: false, spanBatchTime: nil, activateSpanBatch: false},
{name: "RegolithAtGenesis", regolithTime: &zeroTime, activateRegolith: true}, {name: "RegolithAtGenesis", regolithTime: &zeroTime, activateRegolith: true, spanBatchTime: nil, activateSpanBatch: false},
{name: "RegolithAfterGenesis", regolithTime: &futureTime, activateRegolith: true}, {name: "RegolithAfterGenesis", regolithTime: &futureTime, activateRegolith: true, spanBatchTime: nil, activateSpanBatch: false},
{name: "NoSpanBatch", regolithTime: &zeroTime, activateRegolith: true, spanBatchTime: nil, activateSpanBatch: false},
{name: "NotYetSpanBatch", regolithTime: &zeroTime, activateRegolith: true,
spanBatchTime: &farFutureTime, activateSpanBatch: false},
{name: "SpanBatchAtGenesis", regolithTime: &zeroTime, activateRegolith: true,
spanBatchTime: &zeroTime, activateSpanBatch: true},
{name: "SpanBatchAfterGenesis", regolithTime: &zeroTime, activateRegolith: true,
spanBatchTime: &futureTime, activateSpanBatch: true},
} }
for _, test := range tests { for _, test := range tests {
test := test // Use a fixed reference as the tests run in parallel test := test // Use a fixed reference as the tests run in parallel
...@@ -45,10 +54,11 @@ func TestCrossLayerUser(t *testing.T) { ...@@ -45,10 +54,11 @@ func TestCrossLayerUser(t *testing.T) {
} }
} }
func runCrossLayerUserTest(gt *testing.T, test regolithScheduledTest) { func runCrossLayerUserTest(gt *testing.T, test hardforkScheduledTest) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime dp.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = test.spanBatchTime
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
......
...@@ -4,13 +4,15 @@ import ( ...@@ -4,13 +4,15 @@ import (
"context" "context"
"path/filepath" "path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -32,7 +34,7 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu ...@@ -32,7 +34,7 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu
return c return c
} }
func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Client bind.ContractCaller, l1Endpoint string, l2Endpoint string, options ...challenger.Option) *HonestHelper { func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Client *ethclient.Client, l1Endpoint string, l2Endpoint string, options ...challenger.Option) *HonestHelper {
opts := []challenger.Option{ opts := []challenger.Option{
challenger.WithCannon(g.t, rollupCfg, l2Genesis, l2Endpoint), challenger.WithCannon(g.t, rollupCfg, l2Genesis, l2Endpoint),
challenger.WithFactoryAddress(g.factoryAddr), challenger.WithFactoryAddress(g.factoryAddr),
...@@ -42,7 +44,9 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol ...@@ -42,7 +44,9 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...) cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...)
logger := testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace") logger := testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace")
maxDepth := g.MaxDepth(ctx) maxDepth := g.MaxDepth(ctx)
provider, err := cannon.NewTraceProvider(ctx, logger, metrics.NoopMetrics, cfg, l1Client, filepath.Join(cfg.Datadir, "honest"), g.addr, uint64(maxDepth)) gameContract, err := contracts.NewFaultDisputeGameContract(g.addr, batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize))
g.require.NoError(err, "Create game contract bindings")
provider, err := cannon.NewTraceProvider(ctx, logger, metrics.NoopMetrics, cfg, gameContract, filepath.Join(cfg.Datadir, "honest"), uint64(maxDepth))
g.require.NoError(err, "create cannon trace provider") g.require.NoError(err, "create cannon trace provider")
return &HonestHelper{ return &HonestHelper{
......
...@@ -59,7 +59,6 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { ...@@ -59,7 +59,6 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
deployConfig.L1BlockTime = tp.L1BlockTime deployConfig.L1BlockTime = tp.L1BlockTime
deployConfig.L2GenesisRegolithTimeOffset = nil deployConfig.L2GenesisRegolithTimeOffset = nil
deployConfig.L2GenesisCanyonTimeOffset = CanyonTimeOffset() deployConfig.L2GenesisCanyonTimeOffset = CanyonTimeOffset()
deployConfig.L2GenesisSpanBatchTimeOffset = SpanBatchTimeOffset()
require.NoError(t, deployConfig.Check()) require.NoError(t, deployConfig.Check())
require.Equal(t, addresses.Batcher, deployConfig.BatchSenderAddress) require.Equal(t, addresses.Batcher, deployConfig.BatchSenderAddress)
...@@ -186,14 +185,6 @@ func SystemConfigFromDeployConfig(deployConfig *genesis.DeployConfig) eth.System ...@@ -186,14 +185,6 @@ func SystemConfigFromDeployConfig(deployConfig *genesis.DeployConfig) eth.System
} }
} }
func SpanBatchTimeOffset() *hexutil.Uint64 {
if os.Getenv("OP_E2E_USE_SPAN_BATCH") == "true" {
offset := hexutil.Uint64(0)
return &offset
}
return nil
}
func CanyonTimeOffset() *hexutil.Uint64 { func CanyonTimeOffset() *hexutil.Uint64 {
if os.Getenv("OP_E2E_USE_CANYON") == "true" { if os.Getenv("OP_E2E_USE_CANYON") == "true" {
offset := hexutil.Uint64(0) offset := hexutil.Uint64(0)
......
...@@ -88,7 +88,6 @@ func DefaultSystemConfig(t *testing.T) SystemConfig { ...@@ -88,7 +88,6 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
deployConfig := config.DeployConfig.Copy() deployConfig := config.DeployConfig.Copy()
deployConfig.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix()) deployConfig.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix())
deployConfig.L2GenesisCanyonTimeOffset = e2eutils.CanyonTimeOffset() deployConfig.L2GenesisCanyonTimeOffset = e2eutils.CanyonTimeOffset()
deployConfig.L2GenesisSpanBatchTimeOffset = e2eutils.SpanBatchTimeOffset()
require.NoError(t, deployConfig.Check(), "Deploy config is invalid, do you need to run make devnet-allocs?") require.NoError(t, deployConfig.Check(), "Deploy config is invalid, do you need to run make devnet-allocs?")
l1Deployments := config.L1Deployments.Copy() l1Deployments := config.L1Deployments.Copy()
require.NoError(t, l1Deployments.Check()) require.NoError(t, l1Deployments.Check())
...@@ -684,7 +683,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -684,7 +683,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
} }
var batchType uint = derive.SingularBatchType var batchType uint = derive.SingularBatchType
if os.Getenv("OP_E2E_USE_SPAN_BATCH") == "true" { if cfg.DeployConfig.L2GenesisSpanBatchTimeOffset != nil && *cfg.DeployConfig.L2GenesisSpanBatchTimeOffset == hexutil.Uint64(0) {
batchType = derive.SpanBatchType batchType = derive.SpanBatchType
} }
batcherMaxL1TxSizeBytes := cfg.BatcherMaxL1TxSizeBytes batcherMaxL1TxSizeBytes := cfg.BatcherMaxL1TxSizeBytes
......
...@@ -15,25 +15,42 @@ import ( ...@@ -15,25 +15,42 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestVerifyL2OutputRoot(t *testing.T) { func TestVerifyL2OutputRoot(t *testing.T) {
testVerifyL2OutputRoot(t, false) testVerifyL2OutputRoot(t, false, false)
}
func TestVerifyL2OutputRootSpanBatch(t *testing.T) {
testVerifyL2OutputRoot(t, false, true)
} }
func TestVerifyL2OutputRootDetached(t *testing.T) { func TestVerifyL2OutputRootDetached(t *testing.T) {
testVerifyL2OutputRoot(t, true) testVerifyL2OutputRoot(t, true, false)
}
func TestVerifyL2OutputRootDetachedSpanBatch(t *testing.T) {
testVerifyL2OutputRoot(t, true, true)
} }
func TestVerifyL2OutputRootEmptyBlock(t *testing.T) { func TestVerifyL2OutputRootEmptyBlock(t *testing.T) {
testVerifyL2OutputRootEmptyBlock(t, false) testVerifyL2OutputRootEmptyBlock(t, false, false)
}
func TestVerifyL2OutputRootEmptyBlockSpanBatch(t *testing.T) {
testVerifyL2OutputRootEmptyBlock(t, false, true)
} }
func TestVerifyL2OutputRootEmptyBlockDetached(t *testing.T) { func TestVerifyL2OutputRootEmptyBlockDetached(t *testing.T) {
testVerifyL2OutputRootEmptyBlock(t, true) testVerifyL2OutputRootEmptyBlock(t, true, false)
}
func TestVerifyL2OutputRootEmptyBlockDetachedSpanBatch(t *testing.T) {
testVerifyL2OutputRootEmptyBlock(t, true, true)
} }
// TestVerifyL2OutputRootEmptyBlock asserts that the program can verify the output root of an empty block // TestVerifyL2OutputRootEmptyBlock asserts that the program can verify the output root of an empty block
...@@ -46,7 +63,7 @@ func TestVerifyL2OutputRootEmptyBlockDetached(t *testing.T) { ...@@ -46,7 +63,7 @@ func TestVerifyL2OutputRootEmptyBlockDetached(t *testing.T) {
// - reboot the batch submitter // - reboot the batch submitter
// - update the state root via a tx // - update the state root via a tx
// - run program // - run program
func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) { func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool, spanBatchActivated bool) {
InitParallel(t) InitParallel(t)
ctx := context.Background() ctx := context.Background()
...@@ -56,6 +73,13 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) { ...@@ -56,6 +73,13 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) {
// Use a small sequencer window size to avoid test timeout while waiting for empty blocks // Use a small sequencer window size to avoid test timeout while waiting for empty blocks
// But not too small to ensure that our claim and subsequent state change is published // But not too small to ensure that our claim and subsequent state change is published
cfg.DeployConfig.SequencerWindowSize = 16 cfg.DeployConfig.SequencerWindowSize = 16
if spanBatchActivated {
// Activate span batch hard fork
minTs := hexutil.Uint64(0)
cfg.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
} else {
cfg.DeployConfig.L2GenesisSpanBatchTimeOffset = nil
}
sys, err := cfg.Start(t) sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system") require.Nil(t, err, "Error starting up system")
...@@ -147,13 +171,20 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) { ...@@ -147,13 +171,20 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) {
}) })
} }
func testVerifyL2OutputRoot(t *testing.T, detached bool) { func testVerifyL2OutputRoot(t *testing.T, detached bool, spanBatchActivated bool) {
InitParallel(t) InitParallel(t)
ctx := context.Background() ctx := context.Background()
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
// We don't need a verifier - just the sequencer is enough // We don't need a verifier - just the sequencer is enough
delete(cfg.Nodes, "verifier") delete(cfg.Nodes, "verifier")
if spanBatchActivated {
// Activate span batch hard fork
minTs := hexutil.Uint64(0)
cfg.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
} else {
cfg.DeployConfig.L2GenesisSpanBatchTimeOffset = nil
}
sys, err := cfg.Start(t) sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system") require.Nil(t, err, "Error starting up system")
......
...@@ -45,6 +45,31 @@ import ( ...@@ -45,6 +45,31 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
// TestSystemBatchType run each system e2e test case in singular batch mode and span batch mode.
// If the test case tests batch submission and advancing safe head, it should be tested in both singular and span batch mode.
func TestSystemBatchType(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, spanBatchTimeOffset *hexutil.Uint64)
}{
{"StopStartBatcher", StopStartBatcher},
}
for _, test := range tests {
test := test
t.Run(test.name+"_SingularBatch", func(t *testing.T) {
test.f(t, nil)
})
}
spanBatchTimeOffset := hexutil.Uint64(0)
for _, test := range tests {
test := test
t.Run(test.name+"_SpanBatch", func(t *testing.T) {
test.f(t, &spanBatchTimeOffset)
})
}
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
if config.ExternalL2Shim != "" { if config.ExternalL2Shim != "" {
fmt.Println("Running tests with external L2 process adapter at ", config.ExternalL2Shim) fmt.Println("Running tests with external L2 process adapter at ", config.ExternalL2Shim)
...@@ -1222,10 +1247,11 @@ func TestFees(t *testing.T) { ...@@ -1222,10 +1247,11 @@ func TestFees(t *testing.T) {
require.Equal(t, balanceDiff, totalFee, "balances should add up") require.Equal(t, balanceDiff, totalFee, "balances should add up")
} }
func TestStopStartBatcher(t *testing.T) { func StopStartBatcher(t *testing.T, spanBatchTimeOffset *hexutil.Uint64) {
InitParallel(t) InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisSpanBatchTimeOffset = spanBatchTimeOffset
sys, err := cfg.Start(t) sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system") require.Nil(t, err, "Error starting up system")
defer sys.Close() defer sys.Close()
......
bin bin
# config files
genesis.json
jwt.txt
rollup.json
...@@ -28,7 +28,7 @@ var Subcommands = cli.Commands{ ...@@ -28,7 +28,7 @@ var Subcommands = cli.Commands{
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "deploy-config", Name: "deploy-config",
Usage: "Path to hardhat deploy config file", Usage: "Path to deploy config file",
Required: true, Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
...@@ -91,6 +91,9 @@ var Subcommands = cli.Commands{ ...@@ -91,6 +91,9 @@ var Subcommands = cli.Commands{
{ {
Name: "l2", Name: "l2",
Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network", Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network",
Description: "Generating the L2 genesis depends on knowledge of L1 contract addresses for the bridge to be secure. " +
"A deploy config and either a deployment directory or an L1 deployments file are used to create the L2 genesis. " +
"The deploy directory and L1 deployments file are generated by the L1 contract deployments.",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "l1-rpc", Name: "l1-rpc",
...@@ -99,10 +102,15 @@ var Subcommands = cli.Commands{ ...@@ -99,10 +102,15 @@ var Subcommands = cli.Commands{
&cli.StringFlag{ &cli.StringFlag{
Name: "deploy-config", Name: "deploy-config",
Usage: "Path to deploy config file", Usage: "Path to deploy config file",
Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "deployment-dir", Name: "deployment-dir",
Usage: "Path to network deployment directory", Usage: "Path to network deployment directory. Cannot be used with --l1-deployments",
},
&cli.StringFlag{
Name: "l1-deployments",
Usage: "Path to L1 deployments JSON file. Cannot be used with --deployment-dir",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "outfile.l2", Name: "outfile.l2",
...@@ -122,10 +130,16 @@ var Subcommands = cli.Commands{ ...@@ -122,10 +130,16 @@ var Subcommands = cli.Commands{
} }
deployDir := ctx.String("deployment-dir") deployDir := ctx.String("deployment-dir")
if deployDir == "" { l1Deployments := ctx.String("l1-deployments")
return errors.New("Must specify --deployment-dir")
if deployDir != "" && l1Deployments != "" {
return errors.New("cannot specify both --deployment-dir and --l1-deployments")
}
if deployDir == "" && l1Deployments == "" {
return errors.New("must specify either --deployment-dir or --l1-deployments")
} }
if deployDir != "" {
log.Info("Deployment directory", "path", deployDir) log.Info("Deployment directory", "path", deployDir)
depPath, network := filepath.Split(deployDir) depPath, network := filepath.Split(deployDir)
hh, err := hardhat.New(network, nil, []string{depPath}) hh, err := hardhat.New(network, nil, []string{depPath})
...@@ -137,6 +151,16 @@ var Subcommands = cli.Commands{ ...@@ -137,6 +151,16 @@ var Subcommands = cli.Commands{
if err := config.GetDeployedAddresses(hh); err != nil { if err := config.GetDeployedAddresses(hh); err != nil {
return err return err
} }
}
if l1Deployments != "" {
log.Info("L1 deployments", "path", l1Deployments)
deployments, err := genesis.NewL1Deployments(l1Deployments)
if err != nil {
return err
}
config.SetDeployments(deployments)
}
client, err := ethclient.Dial(ctx.String("l1-rpc")) client, err := ethclient.Dial(ctx.String("l1-rpc"))
if err != nil { if err != nil {
......
...@@ -2,6 +2,7 @@ package cli ...@@ -2,6 +2,7 @@ package cli
import ( import (
"fmt" "fmt"
"strings"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -19,7 +20,7 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { ...@@ -19,7 +20,7 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) {
if key != "" { if key != "" {
// Mnemonics are bad because they leak *all* keys when they leak. // Mnemonics are bad because they leak *all* keys when they leak.
// Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions). // Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions).
priv, err := crypto.HexToECDSA(key) priv, err := crypto.HexToECDSA(strings.TrimPrefix(key, "0x"))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read batch submitter key: %w", err) return nil, fmt.Errorf("failed to read batch submitter key: %w", err)
} }
......
...@@ -326,6 +326,12 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti ...@@ -326,6 +326,12 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti
return pubsub.ValidationReject return pubsub.ValidationReject
} }
// [REJECT] if a V2 Block has non-empty withdrawals
if blockVersion == eth.BlockV2 && len(*payload.Withdrawals) != 0 {
log.Warn("payload is on v2 topic, but has non-empty withdrawals", "bad_hash", payload.BlockHash.String(), "withdrawal_count", len(*payload.Withdrawals))
return pubsub.ValidationReject
}
seen, ok := blockHeightLRU.Get(uint64(payload.BlockNumber)) seen, ok := blockHeightLRU.Get(uint64(payload.BlockNumber))
if !ok { if !ok {
seen = new(seenBlocks) seen = new(seenBlocks)
......
package p2p package p2p
import ( import (
"bytes"
"context" "context"
"fmt"
"math/big" "math/big"
"testing" "testing"
"time"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/golang/snappy"
// "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub "github.com/libp2p/go-libp2p-pubsub"
pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -89,3 +99,65 @@ func TestVerifyBlockSignature(t *testing.T) { ...@@ -89,3 +99,65 @@ func TestVerifyBlockSignature(t *testing.T) {
require.Equal(t, pubsub.ValidationIgnore, result) require.Equal(t, pubsub.ValidationIgnore, result)
}) })
} }
func createSignedP2Payload(payload *eth.ExecutionPayload, signer Signer, l2ChainID *big.Int) ([]byte, error) {
var buf bytes.Buffer
buf.Write(make([]byte, 65))
if _, err := payload.MarshalSSZ(&buf); err != nil {
return nil, fmt.Errorf("failed to encoded execution payload to publish: %w", err)
}
data := buf.Bytes()
payloadData := data[65:]
sig, err := signer.Sign(context.TODO(), SigningDomainBlocksV1, l2ChainID, payloadData)
if err != nil {
return nil, fmt.Errorf("failed to sign execution payload with signer: %w", err)
}
copy(data[:65], sig[:])
// compress the full message
// This also copies the data, freeing up the original buffer to go back into the pool
return snappy.Encode(nil, data), nil
}
// TestBlockValidator does some very basic tests of the p2p block validation logic
func TestBlockValidator(t *testing.T) {
// Params Set 1: Create the validation function
cfg := &rollup.Config{
L2ChainID: big.NewInt(100),
}
secrets, err := e2eutils.DefaultMnemonicConfig.Secrets()
require.NoError(t, err)
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)}
// valFnV1 := BuildBlocksValidator(testlog.Logger(t, log.LvlCrit), rollupCfg, runCfg, eth.BlockV1)
valFnV2 := BuildBlocksValidator(testlog.Logger(t, log.LvlCrit), cfg, runCfg, eth.BlockV2)
// Params Set 2: Call the validation function
peerID := peer.ID("foo")
// Valid Case
payload := eth.ExecutionPayload{
Timestamp: hexutil.Uint64(time.Now().Unix()),
Withdrawals: &types.Withdrawals{},
}
payload.BlockHash, _ = payload.CheckBlockHash() // hack to generate the block hash easily.
data, err := createSignedP2Payload(&payload, signer, cfg.L2ChainID)
require.NoError(t, err)
message := &pubsub.Message{Message: &pubsub_pb.Message{Data: data}}
res := valFnV2(context.TODO(), peerID, message)
require.Equal(t, res, pubsub.ValidationAccept)
// Invalid because non-empty withdrawals when Canyon is active
payload = eth.ExecutionPayload{
Timestamp: hexutil.Uint64(time.Now().Unix()),
Withdrawals: &types.Withdrawals{&types.Withdrawal{Index: 1, Validator: 1}},
}
payload.BlockHash, _ = payload.CheckBlockHash()
data, err = createSignedP2Payload(&payload, signer, cfg.L2ChainID)
require.NoError(t, err)
message = &pubsub.Message{Message: &pubsub_pb.Message{Data: data}}
res = valFnV2(context.TODO(), peerID, message)
require.Equal(t, res, pubsub.ValidationReject)
}
...@@ -110,5 +110,9 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { ...@@ -110,5 +110,9 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) {
cfg.ChannelTimeout = 120 cfg.ChannelTimeout = 120
cfg.MaxSequencerDrift = 1200 cfg.MaxSequencerDrift = 1200
} }
if chainID == pgnSepolia {
cfg.MaxSequencerDrift = 1000
cfg.SeqWindowSize = 7200
}
return cfg, nil return cfg, nil
} }
...@@ -20,6 +20,7 @@ type BlockInfo interface { ...@@ -20,6 +20,7 @@ type BlockInfo interface {
BaseFee() *big.Int BaseFee() *big.Int
ReceiptHash() common.Hash ReceiptHash() common.Hash
GasUsed() uint64 GasUsed() uint64
GasLimit() uint64
// HeaderRLP returns the RLP of the block header as per consensus rules // HeaderRLP returns the RLP of the block header as per consensus rules
// Returns an error if the header RLP could not be written // Returns an error if the header RLP could not be written
...@@ -100,6 +101,10 @@ func (h headerBlockInfo) GasUsed() uint64 { ...@@ -100,6 +101,10 @@ func (h headerBlockInfo) GasUsed() uint64 {
return h.Header.GasUsed return h.Header.GasUsed
} }
func (h headerBlockInfo) GasLimit() uint64 {
return h.Header.GasLimit
}
func (h headerBlockInfo) HeaderRLP() ([]byte, error) { func (h headerBlockInfo) HeaderRLP() ([]byte, error) {
return rlp.EncodeToBytes(h.Header) return rlp.EncodeToBytes(h.Header)
} }
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -86,6 +87,17 @@ func toCallArg(msg ethereum.CallMsg) interface{} { ...@@ -86,6 +87,17 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
return arg return arg
} }
func (c *ContractCall) ToTxCandidate() (txmgr.TxCandidate, error) {
data, err := c.Pack()
if err != nil {
return txmgr.TxCandidate{}, fmt.Errorf("failed to pack arguments: %w", err)
}
return txmgr.TxCandidate{
TxData: data,
To: &c.Addr,
}, nil
}
type CallResult struct { type CallResult struct {
out []interface{} out []interface{}
} }
...@@ -117,3 +129,7 @@ func (c *CallResult) GetAddress(i int) common.Address { ...@@ -117,3 +129,7 @@ func (c *CallResult) GetAddress(i int) common.Address {
func (c *CallResult) GetBigInt(i int) *big.Int { func (c *CallResult) GetBigInt(i int) *big.Int {
return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int) return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int)
} }
func (c *CallResult) GetStruct(i int, target interface{}) {
abi.ConvertType(c.out[i], target)
}
...@@ -30,6 +30,22 @@ func TestContractCall_ToCallArgs(t *testing.T) { ...@@ -30,6 +30,22 @@ func TestContractCall_ToCallArgs(t *testing.T) {
require.NotContains(t, argMap, "gasPrice") require.NotContains(t, argMap, "gasPrice")
} }
func TestContractCall_ToTxCandidate(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err)
call := NewContractCall(testAbi, addr, "approve", common.Address{0xcc}, big.NewInt(1234444))
candidate, err := call.ToTxCandidate()
require.NoError(t, err)
require.Equal(t, candidate.To, &addr)
expectedData, err := call.Pack()
require.NoError(t, err)
require.Equal(t, candidate.TxData, expectedData)
require.Nil(t, candidate.Value)
require.Zero(t, candidate.GasLimit)
}
func TestContractCall_Pack(t *testing.T) { func TestContractCall_Pack(t *testing.T) {
addr := common.Address{0xbd} addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi() testAbi, err := bindings.ERC20MetaData.GetAbi()
...@@ -139,6 +155,24 @@ func TestCallResult_GetValues(t *testing.T) { ...@@ -139,6 +155,24 @@ func TestCallResult_GetValues(t *testing.T) {
}, },
expected: big.NewInt(2398423), expected: big.NewInt(2398423),
}, },
{
name: "GetStruct",
getter: func(result *CallResult, i int) interface{} {
out := struct {
a *big.Int
b common.Hash
}{}
result.GetStruct(i, &out)
return out
},
expected: struct {
a *big.Int
b common.Hash
}{
a: big.NewInt(6),
b: common.Hash{0xee},
},
},
} }
for _, test := range tests { for _, test := range tests {
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"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"
...@@ -73,6 +74,11 @@ func (l *AbiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e ...@@ -73,6 +74,11 @@ func (l *AbiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e
return errors.Join(errs...) return errors.Join(errs...)
} }
func (l *AbiBasedRpc) VerifyTxCandidate(candidate txmgr.TxCandidate) {
require.EqualValues(l.t, &l.addr, candidate.To, "Incorrect To address")
l.findExpectedCall(candidate.TxData, batching.BlockLatest.ArgValue())
}
func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error { func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error {
require.Equal(l.t, "eth_call", method) require.Equal(l.t, "eth_call", method)
require.Len(l.t, args, 2) require.Len(l.t, args, 2)
...@@ -82,11 +88,27 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str ...@@ -82,11 +88,27 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str
require.Equal(l.t, &l.addr, callOpts["to"]) require.Equal(l.t, &l.addr, callOpts["to"])
data, ok := callOpts["input"].(hexutil.Bytes) data, ok := callOpts["input"].(hexutil.Bytes)
require.True(l.t, ok) require.True(l.t, ok)
call, abiMethod := l.findExpectedCall(data, actualBlockRef)
output, err := abiMethod.Outputs.Pack(call.outputs...)
require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs)
// I admit I do not understand Go reflection.
// So leverage json.Unmarshal to set the out value correctly.
j, err := json.Marshal(hexutil.Bytes(output))
require.NoError(l.t, err)
require.NoError(l.t, json.Unmarshal(j, out))
return nil
}
func (l *AbiBasedRpc) findExpectedCall(data []byte, actualBlockRef interface{}) (*expectedCall, *abi.Method) {
abiMethod, err := l.abi.MethodById(data[0:4]) abiMethod, err := l.abi.MethodById(data[0:4])
require.NoError(l.t, err) require.NoError(l.t, err)
argData := data[4:] argData := data[4:]
args, err = abiMethod.Inputs.Unpack(argData) args, err := abiMethod.Inputs.Unpack(argData)
require.NoError(l.t, err) require.NoError(l.t, err)
require.Len(l.t, args, len(abiMethod.Inputs)) require.Len(l.t, args, len(abiMethod.Inputs))
...@@ -100,14 +122,5 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str ...@@ -100,14 +122,5 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str
} }
} }
require.NotNilf(l.t, call, "No expected calls to %v at block %v with arguments: %v\nExpected calls: %v", abiMethod.Name, actualBlockRef, args, expectedCalls) require.NotNilf(l.t, call, "No expected calls to %v at block %v with arguments: %v\nExpected calls: %v", abiMethod.Name, actualBlockRef, args, expectedCalls)
return call, abiMethod
output, err := abiMethod.Outputs.Pack(call.outputs...)
require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs)
// I admit I do not understand Go reflection.
// So leverage json.Unmarshal to set the out value correctly.
j, err := json.Marshal(hexutil.Bytes(output))
require.NoError(l.t, err)
require.NoError(l.t, json.Unmarshal(j, out))
return nil
} }
...@@ -51,20 +51,21 @@ func NewEngineClient(client client.RPC, log log.Logger, metrics caching.Metrics, ...@@ -51,20 +51,21 @@ func NewEngineClient(client client.RPC, log log.Logger, metrics caching.Metrics,
// 2. `error` as eth.InputError: the forkchoice state or attributes are not valid. // 2. `error` as eth.InputError: the forkchoice state or attributes are not valid.
// 3. Other types of `error`: temporary RPC errors, like timeouts. // 3. Other types of `error`: temporary RPC errors, like timeouts.
func (s *EngineClient) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceState, attributes *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) { func (s *EngineClient) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceState, attributes *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
e := s.log.New("state", fc, "attr", attributes) llog := s.log.New("state", fc) // local logger
e.Trace("Sharing forkchoice-updated signal") tlog := llog.New("attr", attributes) // trace logger
tlog.Trace("Sharing forkchoice-updated signal")
fcCtx, cancel := context.WithTimeout(ctx, time.Second*5) fcCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel() defer cancel()
var result eth.ForkchoiceUpdatedResult var result eth.ForkchoiceUpdatedResult
err := s.client.CallContext(fcCtx, &result, "engine_forkchoiceUpdatedV2", fc, attributes) err := s.client.CallContext(fcCtx, &result, "engine_forkchoiceUpdatedV2", fc, attributes)
if err == nil { if err == nil {
e.Trace("Shared forkchoice-updated signal") tlog.Trace("Shared forkchoice-updated signal")
if attributes != nil { // block building is optional, we only get a payload ID if we are building a block if attributes != nil { // block building is optional, we only get a payload ID if we are building a block
e.Trace("Received payload id", "payloadId", result.PayloadID) tlog.Trace("Received payload id", "payloadId", result.PayloadID)
} }
return &result, nil return &result, nil
} else { } else {
e.Warn("Failed to share forkchoice-updated signal", "err", err) llog.Warn("Failed to share forkchoice-updated signal", "err", err)
if rpcErr, ok := err.(rpc.Error); ok { if rpcErr, ok := err.(rpc.Error); ok {
code := eth.ErrorCode(rpcErr.ErrorCode()) code := eth.ErrorCode(rpcErr.ErrorCode())
switch code { switch code {
......
This diff is collapsed.
{"name":"post-shanghai-bad-receipts-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"post-shanghai-bad-transactions-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"post-shanghai-bad-transactions-nil","fail":true, "reason": "block tx 0 is nil"}
{"name":"post-shanghai-bad-transactions","fail":true} {"name":"post-shanghai-bad-transactions","fail":true, "reason": "failed to verify transactions list"}
{"name":"post-shanghai-bad-withdrawals-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"post-shanghai-bad-withdrawals-nil","fail":true, "reason": "block withdrawal 0 is null"}
{"name":"post-shanghai-bad-withdrawals","fail":true} {"name":"post-shanghai-bad-withdrawals","fail":true,"reason": "failed to verify withdrawals list"}
{"name":"pre-shanghai-bad-receipts-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"pre-shanghai-bad-transactions-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"pre-shanghai-bad-transactions","fail":true} {"name":"pre-shanghai-bad-transactions","fail":true,"reason": "failed to verify transactions list"}
...@@ -62,18 +62,8 @@ generate_test_vector() { ...@@ -62,18 +62,8 @@ generate_test_vector() {
$mutation_func "$data_file" $metadata_file $mutation_func "$data_file" $metadata_file
} }
mkdir -p data/blocks
mkdir -p data/headers 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 # Headers
generate_test_vector "pre-shanghai-success" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" false success_case 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-transactions" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" false bad_transactions_root
......
...@@ -78,6 +78,10 @@ func (h headerInfo) GasUsed() uint64 { ...@@ -78,6 +78,10 @@ func (h headerInfo) GasUsed() uint64 {
return h.Header.GasUsed return h.Header.GasUsed
} }
func (h headerInfo) GasLimit() uint64 {
return h.Header.GasLimit
}
func (h headerInfo) HeaderRLP() ([]byte, error) { func (h headerInfo) HeaderRLP() ([]byte, error) {
return rlp.EncodeToBytes(h.Header) return rlp.EncodeToBytes(h.Header)
} }
...@@ -103,7 +107,16 @@ type rpcHeader struct { ...@@ -103,7 +107,16 @@ type rpcHeader struct {
BaseFee *hexutil.Big `json:"baseFeePerGas"` BaseFee *hexutil.Big `json:"baseFeePerGas"`
// WithdrawalsRoot was added by EIP-4895 and is ignored in legacy headers. // WithdrawalsRoot was added by EIP-4895 and is ignored in legacy headers.
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot"` WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed,omitempty"`
// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas,omitempty"`
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"`
// untrusted info included by RPC, may have to be checked // untrusted info included by RPC, may have to be checked
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
...@@ -156,6 +169,10 @@ func (hdr *rpcHeader) createGethHeader() *types.Header { ...@@ -156,6 +169,10 @@ func (hdr *rpcHeader) createGethHeader() *types.Header {
Nonce: hdr.Nonce, Nonce: hdr.Nonce,
BaseFee: (*big.Int)(hdr.BaseFee), BaseFee: (*big.Int)(hdr.BaseFee),
WithdrawalsHash: hdr.WithdrawalsRoot, WithdrawalsHash: hdr.WithdrawalsRoot,
// Cancun
BlobGasUsed: (*uint64)(hdr.BlobGasUsed),
ExcessBlobGas: (*uint64)(hdr.ExcessBlobGas),
ParentBeaconRoot: hdr.ParentBeaconRoot,
} }
} }
...@@ -185,7 +202,7 @@ func (block *rpcBlock) verify() error { ...@@ -185,7 +202,7 @@ func (block *rpcBlock) verify() error {
} }
for i, tx := range block.Transactions { for i, tx := range block.Transactions {
if tx == nil { if tx == nil {
return fmt.Errorf("block tx %d is null", i) return fmt.Errorf("block tx %d is nil", 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 {
......
...@@ -15,6 +15,7 @@ var blocksTestdata embed.FS ...@@ -15,6 +15,7 @@ var blocksTestdata embed.FS
type testMetadata struct { type testMetadata struct {
Name string `json:"name"` Name string `json:"name"`
Fail bool `json:"fail,omitempty"` Fail bool `json:"fail,omitempty"`
Reason string `json:"reason,omitempty"`
} }
func readJsonTestdata(t *testing.T, name string, dest any) { func readJsonTestdata(t *testing.T, name string, dest any) {
...@@ -67,6 +68,7 @@ func TestBlockJSON(t *testing.T) { ...@@ -67,6 +68,7 @@ func TestBlockJSON(t *testing.T) {
err := block.verify() err := block.verify()
if metadata.Fail { if metadata.Fail {
require.NotNil(t, err, "expecting verification error") require.NotNil(t, err, "expecting verification error")
require.ErrorContains(t, err, metadata.Reason, "validation failed for incorrect reason")
} else { } else {
require.NoError(t, err, "verification should pass") require.NoError(t, err, "verification should pass")
} }
......
...@@ -23,6 +23,7 @@ type MockBlockInfo struct { ...@@ -23,6 +23,7 @@ type MockBlockInfo struct {
InfoBaseFee *big.Int InfoBaseFee *big.Int
InfoReceiptRoot common.Hash InfoReceiptRoot common.Hash
InfoGasUsed uint64 InfoGasUsed uint64
InfoGasLimit uint64
InfoHeaderRLP []byte InfoHeaderRLP []byte
} }
...@@ -66,6 +67,10 @@ func (l *MockBlockInfo) GasUsed() uint64 { ...@@ -66,6 +67,10 @@ func (l *MockBlockInfo) GasUsed() uint64 {
return l.InfoGasUsed return l.InfoGasUsed
} }
func (l *MockBlockInfo) GasLimit() uint64 {
return l.InfoGasLimit
}
func (l *MockBlockInfo) ID() eth.BlockID { func (l *MockBlockInfo) ID() eth.BlockID {
return eth.BlockID{Hash: l.InfoHash, Number: l.InfoNum} return eth.BlockID{Hash: l.InfoHash, Number: l.InfoNum}
} }
......
...@@ -4,12 +4,9 @@ package mocks ...@@ -4,12 +4,9 @@ package mocks
import ( import (
context "context" context "context"
big "math/big"
common "github.com/ethereum/go-ethereum/common" common "github.com/ethereum/go-ethereum/common"
ethereum "github.com/ethereum/go-ethereum"
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr" txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
...@@ -46,32 +43,6 @@ func (_m *TxManager) BlockNumber(ctx context.Context) (uint64, error) { ...@@ -46,32 +43,6 @@ func (_m *TxManager) BlockNumber(ctx context.Context) (uint64, error) {
return r0, r1 return r0, r1
} }
// Call provides a mock function with given fields: ctx, msg, blockNumber
func (_m *TxManager) Call(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
ret := _m.Called(ctx, msg, blockNumber)
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)); ok {
return rf(ctx, msg, blockNumber)
}
if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) []byte); ok {
r0 = rf(ctx, msg, blockNumber)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg, *big.Int) error); ok {
r1 = rf(ctx, msg, blockNumber)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// From provides a mock function with given fields: // From provides a mock function with given fields:
func (_m *TxManager) From() common.Address { func (_m *TxManager) From() common.Address {
ret := _m.Called() ret := _m.Called()
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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