Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
dfee56f5
Unverified
Commit
dfee56f5
authored
Jun 12, 2023
by
mergify[bot]
Committed by
GitHub
Jun 12, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' into extradata
parents
cedeb4ec
71aadcfc
Changes
35
Show whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
14 additions
and
6071 deletions
+14
-6071
go.mod
go.mod
+1
-3
go.sum
go.sum
+0
-7
api_test.go
indexer/api/api_test.go
+10
-6
Dockerfile
op-chain-ops/Dockerfile
+0
-20
main.go
op-chain-ops/cmd/check-migration/main.go
+0
-211
main.go
op-chain-ops/cmd/inject-mints/main.go
+0
-88
main.go
op-chain-ops/cmd/op-migrate/main.go
+0
-286
main.go
op-chain-ops/cmd/rollover/main.go
+0
-336
main.go
op-chain-ops/cmd/withdrawals/main.go
+0
-1192
migrate.go
op-chain-ops/crossdomain/migrate.go
+0
-45
params.go
op-chain-ops/crossdomain/params.go
+0
-27
precheck.go
op-chain-ops/crossdomain/precheck.go
+0
-113
precheck_test.go
op-chain-ops/crossdomain/precheck_test.go
+0
-134
types.go
op-chain-ops/crossdomain/types.go
+0
-44
types_test.go
op-chain-ops/crossdomain/types_test.go
+0
-57
withdrawals.go
op-chain-ops/crossdomain/withdrawals.go
+0
-133
withdrawals_test.go
op-chain-ops/crossdomain/withdrawals_test.go
+0
-278
witness.go
op-chain-ops/crossdomain/witness.go
+0
-243
witness_test.go
op-chain-ops/crossdomain/witness_test.go
+0
-213
addresses.go
op-chain-ops/ether/addresses.go
+0
-136
cli.go
op-chain-ops/ether/cli.go
+0
-32
db.go
op-chain-ops/ether/db.go
+0
-35
migrate.go
op-chain-ops/ether/migrate.go
+0
-264
migrate_test.go
op-chain-ops/ether/migrate_test.go
+0
-321
storage.go
op-chain-ops/ether/storage.go
+0
-32
check.go
op-chain-ops/genesis/check.go
+0
-677
db_migration.go
op-chain-ops/genesis/db_migration.go
+0
-322
genesis.go
op-chain-ops/genesis/genesis.go
+3
-0
action.go
op-chain-ops/genesis/migration_action/action.go
+0
-96
setters.go
op-chain-ops/genesis/setters.go
+0
-53
setters_test.go
op-chain-ops/genesis/setters_test.go
+0
-51
test_util.go
op-chain-ops/genesis/test_util.go
+0
-51
state_iterator.go
op-chain-ops/util/state_iterator.go
+0
-161
state_iterator_test.go
op-chain-ops/util/state_iterator_test.go
+0
-211
util.go
op-chain-ops/util/util.go
+0
-193
No files found.
go.mod
View file @
dfee56f5
...
@@ -27,11 +27,9 @@ require (
...
@@ -27,11 +27,9 @@ require (
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/prometheus/client_golang v1.14.0
github.com/prometheus/client_golang v1.14.0
github.com/schollz/progressbar/v3 v3.13.0
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.9
github.com/urfave/cli v1.22.9
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
golang.org/x/crypto v0.6.0
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/sync v0.1.0
golang.org/x/sync v0.1.0
golang.org/x/term v0.6.0
golang.org/x/term v0.6.0
...
@@ -120,7 +118,6 @@ require (
...
@@ -120,7 +118,6 @@ require (
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
...
@@ -167,6 +164,7 @@ require (
...
@@ -167,6 +164,7 @@ require (
go.uber.org/fx v1.19.1 // indirect
go.uber.org/fx v1.19.1 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/sys v0.6.0 // indirect
...
...
go.sum
View file @
dfee56f5
...
@@ -360,7 +360,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
...
@@ -360,7 +360,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
...
@@ -474,8 +473,6 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv
...
@@ -474,8 +473,6 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
...
@@ -610,8 +607,6 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
...
@@ -610,8 +607,6 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/schollz/progressbar/v3 v3.13.0 h1:9TeeWRcjW2qd05I8Kf9knPkW4vLM/hYoa6z9ABvxje8=
github.com/schollz/progressbar/v3 v3.13.0/go.mod h1:ZBYnSuLAX2LU8P8UiKN/KgF2DY58AJC8yfVYLPC8Ly4=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
...
@@ -867,13 +862,11 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
...
@@ -867,13 +862,11 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
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/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.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
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=
...
...
indexer/api/api_test.go
View file @
dfee56f5
...
@@ -5,23 +5,27 @@ import (
...
@@ -5,23 +5,27 @@ import (
"net/http/httptest"
"net/http/httptest"
"testing"
"testing"
"github.com/google/uuid"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/assert"
)
)
// MockBridgeView mocks the BridgeView interface
// MockBridgeView mocks the BridgeView interface
type
MockBridgeView
struct
{}
type
MockBridgeView
struct
{}
const
(
guid1
=
"8408b6d2-7c90-4cfc-8604-b2204116cb6a"
guid2
=
"8408b6d2-7c90-4cfc-8604-b2204116cb6b"
)
// DepositsByAddress mocks returning deposits by an address
// DepositsByAddress mocks returning deposits by an address
func
(
mbv
*
MockBridgeView
)
DepositsByAddress
(
address
common
.
Address
)
([]
*
database
.
DepositWithTransactionHash
,
error
)
{
func
(
mbv
*
MockBridgeView
)
DepositsByAddress
(
address
common
.
Address
)
([]
*
database
.
DepositWithTransactionHash
,
error
)
{
return
[]
*
database
.
DepositWithTransactionHash
{
return
[]
*
database
.
DepositWithTransactionHash
{
{
{
Deposit
:
database
.
Deposit
{
Deposit
:
database
.
Deposit
{
GUID
:
uuid
.
New
(
),
GUID
:
uuid
.
MustParse
(
guid1
),
InitiatedL1EventGUID
:
"mockEventGUID1"
,
InitiatedL1EventGUID
:
guid2
,
Tx
:
database
.
Transaction
{},
Tx
:
database
.
Transaction
{},
TokenPair
:
database
.
TokenPair
{},
TokenPair
:
database
.
TokenPair
{},
},
},
...
@@ -35,8 +39,8 @@ func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*data
...
@@ -35,8 +39,8 @@ func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*data
return
[]
*
database
.
WithdrawalWithTransactionHashes
{
return
[]
*
database
.
WithdrawalWithTransactionHashes
{
{
{
Withdrawal
:
database
.
Withdrawal
{
Withdrawal
:
database
.
Withdrawal
{
GUID
:
uuid
.
New
(
),
GUID
:
uuid
.
MustParse
(
guid2
),
InitiatedL2EventGUID
:
"mockEventGUID2"
,
InitiatedL2EventGUID
:
guid1
,
WithdrawalHash
:
common
.
HexToHash
(
"0x456"
),
WithdrawalHash
:
common
.
HexToHash
(
"0x456"
),
Tx
:
database
.
Transaction
{},
Tx
:
database
.
Transaction
{},
TokenPair
:
database
.
TokenPair
{},
TokenPair
:
database
.
TokenPair
{},
...
...
op-chain-ops/Dockerfile
deleted
100644 → 0
View file @
cedeb4ec
FROM
golang:1.19.9-alpine3.15 as builder
RUN
apk add
--no-cache
make gcc musl-dev linux-headers git jq bash
COPY
./op-chain-ops /app/op-chain-ops
COPY
./op-bindings /app/op-bindings
COPY
./op-node /app/op-node
COPY
./go.mod /app/go.mod
COPY
./go.sum /app/go.sum
COPY
./.git /app/.git
WORKDIR
/app/op-chain-ops
RUN
make op-migrate
FROM
alpine:3.15
COPY
--from=builder /app/op-chain-ops/bin/op-migrate /usr/local/bin
ENTRYPOINT
["op-migrate"]
op-chain-ops/cmd/check-migration/main.go
deleted
100644 → 0
View file @
cedeb4ec
package
main
import
(
"context"
"fmt"
"math/big"
"os"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/db"
"github.com/mattn/go-isatty"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/hardhat"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
)
func
main
()
{
log
.
Root
()
.
SetHandler
(
log
.
StreamHandler
(
os
.
Stderr
,
log
.
TerminalFormat
(
isatty
.
IsTerminal
(
os
.
Stderr
.
Fd
()))))
app
:=
&
cli
.
App
{
Name
:
"check-migration"
,
Usage
:
"Run sanity checks on a migrated database"
,
Flags
:
[]
cli
.
Flag
{
&
cli
.
StringFlag
{
Name
:
"l1-rpc-url"
,
Value
:
"http://127.0.0.1:8545"
,
Usage
:
"RPC URL for an L1 Node"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"ovm-addresses"
,
Usage
:
"Path to ovm-addresses.json"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"ovm-allowances"
,
Usage
:
"Path to ovm-allowances.json"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"ovm-messages"
,
Usage
:
"Path to ovm-messages.json"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"witness-file"
,
Usage
:
"Path to witness file"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"db-path"
,
Usage
:
"Path to database"
,
Required
:
true
,
},
cli
.
StringFlag
{
Name
:
"deploy-config"
,
Usage
:
"Path to hardhat deploy config file"
,
Required
:
true
,
},
cli
.
StringFlag
{
Name
:
"network"
,
Usage
:
"Name of hardhat deploy network"
,
Required
:
true
,
},
cli
.
StringFlag
{
Name
:
"hardhat-deployments"
,
Usage
:
"Comma separated list of hardhat deployment directories"
,
Required
:
true
,
},
cli
.
IntFlag
{
Name
:
"db-cache"
,
Usage
:
"LevelDB cache size in mb"
,
Value
:
1024
,
},
cli
.
IntFlag
{
Name
:
"db-handles"
,
Usage
:
"LevelDB number of handles"
,
Value
:
60
,
},
},
Action
:
func
(
ctx
*
cli
.
Context
)
error
{
deployConfig
:=
ctx
.
String
(
"deploy-config"
)
config
,
err
:=
genesis
.
NewDeployConfig
(
deployConfig
)
if
err
!=
nil
{
return
err
}
ovmAddresses
,
err
:=
crossdomain
.
NewAddresses
(
ctx
.
String
(
"ovm-addresses"
))
if
err
!=
nil
{
return
err
}
ovmAllowances
,
err
:=
crossdomain
.
NewAllowances
(
ctx
.
String
(
"ovm-allowances"
))
if
err
!=
nil
{
return
err
}
ovmMessages
,
err
:=
crossdomain
.
NewSentMessageFromJSON
(
ctx
.
String
(
"ovm-messages"
))
if
err
!=
nil
{
return
err
}
evmMessages
,
evmAddresses
,
err
:=
crossdomain
.
ReadWitnessData
(
ctx
.
String
(
"witness-file"
))
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Loaded witness data"
,
"ovmAddresses"
,
len
(
ovmAddresses
),
"evmAddresses"
,
len
(
evmAddresses
),
"ovmAllowances"
,
len
(
ovmAllowances
),
"ovmMessages"
,
len
(
ovmMessages
),
"evmMessages"
,
len
(
evmMessages
),
)
migrationData
:=
crossdomain
.
MigrationData
{
OvmAddresses
:
ovmAddresses
,
EvmAddresses
:
evmAddresses
,
OvmAllowances
:
ovmAllowances
,
OvmMessages
:
ovmMessages
,
EvmMessages
:
evmMessages
,
}
network
:=
ctx
.
String
(
"network"
)
deployments
:=
strings
.
Split
(
ctx
.
String
(
"hardhat-deployments"
),
","
)
hh
,
err
:=
hardhat
.
New
(
network
,
[]
string
{},
deployments
)
if
err
!=
nil
{
return
err
}
l1RpcURL
:=
ctx
.
String
(
"l1-rpc-url"
)
l1Client
,
err
:=
ethclient
.
Dial
(
l1RpcURL
)
if
err
!=
nil
{
return
err
}
var
block
*
types
.
Block
tag
:=
config
.
L1StartingBlockTag
if
tag
.
BlockNumber
!=
nil
{
block
,
err
=
l1Client
.
BlockByNumber
(
context
.
Background
(),
big
.
NewInt
(
tag
.
BlockNumber
.
Int64
()))
}
else
if
tag
.
BlockHash
!=
nil
{
block
,
err
=
l1Client
.
BlockByHash
(
context
.
Background
(),
*
tag
.
BlockHash
)
}
else
{
return
fmt
.
Errorf
(
"invalid l1StartingBlockTag in deploy config: %v"
,
tag
)
}
if
err
!=
nil
{
return
err
}
dbCache
:=
ctx
.
Int
(
"db-cache"
)
dbHandles
:=
ctx
.
Int
(
"db-handles"
)
// Read the required deployment addresses from disk if required
if
err
:=
config
.
GetDeployedAddresses
(
hh
);
err
!=
nil
{
return
err
}
if
err
:=
config
.
Check
();
err
!=
nil
{
return
err
}
postLDB
,
err
:=
db
.
Open
(
ctx
.
String
(
"db-path"
),
dbCache
,
dbHandles
)
if
err
!=
nil
{
return
err
}
if
err
:=
genesis
.
PostCheckMigratedDB
(
postLDB
,
migrationData
,
&
config
.
L1CrossDomainMessengerProxy
,
config
.
L1ChainID
,
config
.
L2ChainID
,
config
.
FinalSystemOwner
,
config
.
ProxyAdminOwner
,
&
derive
.
L1BlockInfo
{
Number
:
block
.
NumberU64
(),
Time
:
block
.
Time
(),
BaseFee
:
block
.
BaseFee
(),
BlockHash
:
block
.
Hash
(),
BatcherAddr
:
config
.
BatchSenderAddress
,
L1FeeOverhead
:
eth
.
Bytes32
(
common
.
BigToHash
(
new
(
big
.
Int
)
.
SetUint64
(
config
.
GasPriceOracleOverhead
))),
L1FeeScalar
:
eth
.
Bytes32
(
common
.
BigToHash
(
new
(
big
.
Int
)
.
SetUint64
(
config
.
GasPriceOracleScalar
))),
},
);
err
!=
nil
{
return
err
}
if
err
:=
postLDB
.
Close
();
err
!=
nil
{
return
err
}
return
nil
},
}
if
err
:=
app
.
Run
(
os
.
Args
);
err
!=
nil
{
log
.
Crit
(
"error in migration"
,
"err"
,
err
)
}
}
op-chain-ops/cmd/inject-mints/main.go
deleted
100644 → 0
View file @
cedeb4ec
package
main
import
(
"fmt"
"os"
"github.com/mattn/go-isatty"
"github.com/ethereum-optimism/optimism/op-chain-ops/db"
"github.com/ethereum-optimism/optimism/op-chain-ops/ether"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/log"
"github.com/schollz/progressbar/v3"
"github.com/urfave/cli"
)
func
main
()
{
lvlHdlr
:=
log
.
StreamHandler
(
os
.
Stderr
,
log
.
TerminalFormat
(
isatty
.
IsTerminal
(
os
.
Stderr
.
Fd
())))
log
.
Root
()
.
SetHandler
(
log
.
LvlFilterHandler
(
log
.
LvlInfo
,
lvlHdlr
))
app
:=
&
cli
.
App
{
Name
:
"inject-mints"
,
Usage
:
"Injects mints into l2geth witness data"
,
Flags
:
[]
cli
.
Flag
{
&
cli
.
StringFlag
{
Name
:
"db-path"
,
Usage
:
"Path to database"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"witness-file-out"
,
Usage
:
"Path to the witness file"
,
Required
:
true
,
},
cli
.
IntFlag
{
Name
:
"db-cache"
,
Usage
:
"LevelDB cache size in mb"
,
Value
:
1024
,
},
cli
.
IntFlag
{
Name
:
"db-handles"
,
Usage
:
"LevelDB number of handles"
,
Value
:
60
,
},
},
Action
:
func
(
ctx
*
cli
.
Context
)
error
{
ldb
,
err
:=
db
.
Open
(
ctx
.
String
(
"db-path"
),
ctx
.
Int
(
"db-cache"
),
ctx
.
Int
(
"db-handles"
))
if
err
!=
nil
{
return
fmt
.
Errorf
(
"error opening db: %w"
,
err
)
}
defer
ldb
.
Close
()
f
,
err
:=
os
.
OpenFile
(
ctx
.
String
(
"witness-file-out"
),
os
.
O_CREATE
|
os
.
O_APPEND
|
os
.
O_WRONLY
,
0600
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"error opening witness file: %w"
,
err
)
}
log
.
Info
(
"Reading mint events from DB"
)
headBlock
:=
rawdb
.
ReadHeadBlock
(
ldb
)
headNum
:=
headBlock
.
NumberU64
()
seenAddrs
:=
make
(
map
[
common
.
Address
]
bool
)
bar
:=
progressbar
.
Default
(
int64
(
headNum
))
var
count
uint64
progressCb
:=
func
(
headNum
uint64
)
{
_
=
bar
.
Add
(
1
)
}
err
=
ether
.
IterateMintEvents
(
ldb
,
headNum
,
func
(
address
common
.
Address
,
headNum
uint64
)
error
{
if
seenAddrs
[
address
]
{
return
nil
}
count
++
seenAddrs
[
address
]
=
true
_
,
err
:=
fmt
.
Fprintf
(
f
,
"ETH|%s
\n
"
,
address
.
Hex
())
return
err
},
progressCb
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"error iterating mint events: %w"
,
err
)
}
log
.
Info
(
"Done"
)
return
nil
},
}
if
err
:=
app
.
Run
(
os
.
Args
);
err
!=
nil
{
log
.
Crit
(
"error in inject-mints"
,
"err"
,
err
)
}
}
op-chain-ops/cmd/op-migrate/main.go
deleted
100644 → 0
View file @
cedeb4ec
package
main
import
(
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/db"
"github.com/mattn/go-isatty"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/hardhat"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
)
func
main
()
{
log
.
Root
()
.
SetHandler
(
log
.
StreamHandler
(
os
.
Stderr
,
log
.
TerminalFormat
(
isatty
.
IsTerminal
(
os
.
Stderr
.
Fd
()))))
app
:=
&
cli
.
App
{
Name
:
"migrate"
,
Usage
:
"Migrate a legacy database"
,
Flags
:
[]
cli
.
Flag
{
&
cli
.
StringFlag
{
Name
:
"l1-rpc-url"
,
Value
:
"http://127.0.0.1:8545"
,
Usage
:
"RPC URL for an L1 Node"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"ovm-addresses"
,
Usage
:
"Path to ovm-addresses.json"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"ovm-allowances"
,
Usage
:
"Path to ovm-allowances.json"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"ovm-messages"
,
Usage
:
"Path to ovm-messages.json"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"witness-file"
,
Usage
:
"Path to witness file"
,
Required
:
true
,
},
&
cli
.
StringFlag
{
Name
:
"db-path"
,
Usage
:
"Path to database"
,
Required
:
true
,
},
cli
.
StringFlag
{
Name
:
"deploy-config"
,
Usage
:
"Path to hardhat deploy config file"
,
Required
:
true
,
},
cli
.
StringFlag
{
Name
:
"network"
,
Usage
:
"Name of hardhat deploy network"
,
Required
:
true
,
},
cli
.
StringFlag
{
Name
:
"hardhat-deployments"
,
Usage
:
"Comma separated list of hardhat deployment directories"
,
Required
:
true
,
},
cli
.
BoolFlag
{
Name
:
"dry-run"
,
Usage
:
"Dry run the upgrade by not committing the database"
,
},
cli
.
BoolFlag
{
Name
:
"no-check"
,
Usage
:
"Do not perform sanity checks. This should only be used for testing"
,
},
cli
.
IntFlag
{
Name
:
"db-cache"
,
Usage
:
"LevelDB cache size in mb"
,
Value
:
1024
,
},
cli
.
IntFlag
{
Name
:
"db-handles"
,
Usage
:
"LevelDB number of handles"
,
Value
:
60
,
},
cli
.
StringFlag
{
Name
:
"rollup-config-out"
,
Usage
:
"Path that op-node config will be written to disk"
,
Value
:
"rollup.json"
,
Required
:
true
,
},
cli
.
BoolFlag
{
Name
:
"post-check-only"
,
Usage
:
"Only perform sanity checks"
,
Required
:
false
,
},
},
Action
:
func
(
ctx
*
cli
.
Context
)
error
{
deployConfig
:=
ctx
.
String
(
"deploy-config"
)
config
,
err
:=
genesis
.
NewDeployConfig
(
deployConfig
)
if
err
!=
nil
{
return
err
}
ovmAddresses
,
err
:=
crossdomain
.
NewAddresses
(
ctx
.
String
(
"ovm-addresses"
))
if
err
!=
nil
{
return
err
}
ovmAllowances
,
err
:=
crossdomain
.
NewAllowances
(
ctx
.
String
(
"ovm-allowances"
))
if
err
!=
nil
{
return
err
}
ovmMessages
,
err
:=
crossdomain
.
NewSentMessageFromJSON
(
ctx
.
String
(
"ovm-messages"
))
if
err
!=
nil
{
return
err
}
evmMessages
,
evmAddresses
,
err
:=
crossdomain
.
ReadWitnessData
(
ctx
.
String
(
"witness-file"
))
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Loaded witness data"
,
"ovmAddresses"
,
len
(
ovmAddresses
),
"evmAddresses"
,
len
(
evmAddresses
),
"ovmAllowances"
,
len
(
ovmAllowances
),
"ovmMessages"
,
len
(
ovmMessages
),
"evmMessages"
,
len
(
evmMessages
),
)
migrationData
:=
crossdomain
.
MigrationData
{
OvmAddresses
:
ovmAddresses
,
EvmAddresses
:
evmAddresses
,
OvmAllowances
:
ovmAllowances
,
OvmMessages
:
ovmMessages
,
EvmMessages
:
evmMessages
,
}
network
:=
ctx
.
String
(
"network"
)
deployments
:=
strings
.
Split
(
ctx
.
String
(
"hardhat-deployments"
),
","
)
hh
,
err
:=
hardhat
.
New
(
network
,
[]
string
{},
deployments
)
if
err
!=
nil
{
return
err
}
l1RpcURL
:=
ctx
.
String
(
"l1-rpc-url"
)
l1Client
,
err
:=
ethclient
.
Dial
(
l1RpcURL
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot dial L1 client: %w"
,
err
)
}
chainId
,
err
:=
l1Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to get L1 ChainID: %w"
,
err
)
}
log
.
Info
(
"L1 ChainID"
,
"chainId"
,
chainId
)
var
block
*
types
.
Block
tag
:=
config
.
L1StartingBlockTag
if
tag
==
nil
{
return
errors
.
New
(
"l1StartingBlockTag cannot be nil"
)
}
log
.
Info
(
"Using L1 Starting Block Tag"
,
"tag"
,
tag
.
String
())
if
number
,
isNumber
:=
tag
.
Number
();
isNumber
{
block
,
err
=
l1Client
.
BlockByNumber
(
context
.
Background
(),
big
.
NewInt
(
number
.
Int64
()))
}
else
if
hash
,
isHash
:=
tag
.
Hash
();
isHash
{
block
,
err
=
l1Client
.
BlockByHash
(
context
.
Background
(),
hash
)
}
else
{
return
fmt
.
Errorf
(
"invalid l1StartingBlockTag in deploy config: %v"
,
tag
)
}
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot fetch L1 starting block tag: %w"
,
err
)
}
dbCache
:=
ctx
.
Int
(
"db-cache"
)
dbHandles
:=
ctx
.
Int
(
"db-handles"
)
dbPath
:=
ctx
.
String
(
"db-path"
)
log
.
Info
(
"Opening database"
,
"dbCache"
,
dbCache
,
"dbHandles"
,
dbHandles
,
"dbPath"
,
dbPath
)
ldb
,
err
:=
db
.
Open
(
dbPath
,
dbCache
,
dbHandles
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot open DB: %w"
,
err
)
}
// Read the required deployment addresses from disk if required
if
err
:=
config
.
GetDeployedAddresses
(
hh
);
err
!=
nil
{
return
err
}
if
err
:=
config
.
Check
();
err
!=
nil
{
return
err
}
dryRun
:=
ctx
.
Bool
(
"dry-run"
)
noCheck
:=
ctx
.
Bool
(
"no-check"
)
if
noCheck
{
panic
(
"must run with check on"
)
}
// Perform the migration
res
,
err
:=
genesis
.
MigrateDB
(
ldb
,
config
,
block
,
&
migrationData
,
!
dryRun
,
noCheck
)
if
err
!=
nil
{
return
err
}
// Close the database handle
if
err
:=
ldb
.
Close
();
err
!=
nil
{
return
err
}
postLDB
,
err
:=
db
.
Open
(
dbPath
,
dbCache
,
dbHandles
)
if
err
!=
nil
{
return
err
}
if
err
:=
genesis
.
PostCheckMigratedDB
(
postLDB
,
migrationData
,
&
config
.
L1CrossDomainMessengerProxy
,
config
.
L1ChainID
,
config
.
L2ChainID
,
config
.
FinalSystemOwner
,
config
.
ProxyAdminOwner
,
&
derive
.
L1BlockInfo
{
Number
:
block
.
NumberU64
(),
Time
:
block
.
Time
(),
BaseFee
:
block
.
BaseFee
(),
BlockHash
:
block
.
Hash
(),
BatcherAddr
:
config
.
BatchSenderAddress
,
L1FeeOverhead
:
eth
.
Bytes32
(
common
.
BigToHash
(
new
(
big
.
Int
)
.
SetUint64
(
config
.
GasPriceOracleOverhead
))),
L1FeeScalar
:
eth
.
Bytes32
(
common
.
BigToHash
(
new
(
big
.
Int
)
.
SetUint64
(
config
.
GasPriceOracleScalar
))),
},
);
err
!=
nil
{
return
err
}
if
err
:=
postLDB
.
Close
();
err
!=
nil
{
return
err
}
opNodeConfig
,
err
:=
config
.
RollupConfig
(
block
,
res
.
TransitionBlockHash
,
res
.
TransitionHeight
)
if
err
!=
nil
{
return
err
}
if
err
:=
writeJSON
(
ctx
.
String
(
"rollup-config-out"
),
opNodeConfig
);
err
!=
nil
{
return
err
}
return
nil
},
}
if
err
:=
app
.
Run
(
os
.
Args
);
err
!=
nil
{
log
.
Crit
(
"error in migration"
,
"err"
,
err
)
}
}
func
writeJSON
(
outfile
string
,
input
interface
{})
error
{
f
,
err
:=
os
.
OpenFile
(
outfile
,
os
.
O_WRONLY
|
os
.
O_CREATE
|
os
.
O_TRUNC
,
0
o755
)
if
err
!=
nil
{
return
err
}
defer
f
.
Close
()
enc
:=
json
.
NewEncoder
(
f
)
enc
.
SetIndent
(
""
,
" "
)
return
enc
.
Encode
(
input
)
}
op-chain-ops/cmd/rollover/main.go
deleted
100644 → 0
View file @
cedeb4ec
package
main
import
(
"context"
"fmt"
"math/big"
"os"
"sync"
"time"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
legacy_bindings
"github.com/ethereum-optimism/optimism/op-bindings/legacy-bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
func
main
()
{
log
.
Root
()
.
SetHandler
(
log
.
StreamHandler
(
os
.
Stderr
,
log
.
TerminalFormat
(
isatty
.
IsTerminal
(
os
.
Stderr
.
Fd
()))))
app
:=
cli
.
NewApp
()
app
.
Name
=
"rollover"
app
.
Usage
=
"Commands for assisting in the rollover of the system"
var
flags
[]
cli
.
Flag
flags
=
append
(
flags
,
util
.
ClientsFlags
...
)
flags
=
append
(
flags
,
util
.
AddressesFlags
...
)
app
.
Commands
=
[]
*
cli
.
Command
{
{
Name
:
"deposits"
,
Usage
:
"Ensures that all deposits have been ingested into L2"
,
Flags
:
flags
,
Action
:
func
(
cliCtx
*
cli
.
Context
)
error
{
clients
,
err
:=
util
.
NewClients
(
cliCtx
)
if
err
!=
nil
{
return
err
}
addresses
,
err
:=
util
.
NewAddresses
(
cliCtx
)
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Requires an archive node"
)
log
.
Info
(
"Connecting to AddressManager"
,
"address"
,
addresses
.
AddressManager
)
addressManager
,
err
:=
bindings
.
NewAddressManager
(
addresses
.
AddressManager
,
clients
.
L1Client
)
if
err
!=
nil
{
return
err
}
for
{
shutoffBlock
,
err
:=
addressManager
.
GetAddress
(
&
bind
.
CallOpts
{},
"DTL_SHUTOFF_BLOCK"
)
if
err
!=
nil
{
return
err
}
if
num
:=
shutoffBlock
.
Big
();
num
.
Cmp
(
common
.
Big0
)
!=
0
{
log
.
Info
(
"DTL_SHUTOFF_BLOCK is set"
,
"number"
,
num
.
Uint64
())
break
}
log
.
Info
(
"DTL_SHUTOFF_BLOCK not set yet"
)
time
.
Sleep
(
3
*
time
.
Second
)
}
shutoffBlock
,
err
:=
addressManager
.
GetAddress
(
&
bind
.
CallOpts
{},
"DTL_SHUTOFF_BLOCK"
)
if
err
!=
nil
{
return
err
}
shutoffHeight
:=
shutoffBlock
.
Big
()
log
.
Info
(
"Connecting to CanonicalTransactionChain"
,
"address"
,
addresses
.
CanonicalTransactionChain
)
ctc
,
err
:=
legacy_bindings
.
NewCanonicalTransactionChain
(
addresses
.
CanonicalTransactionChain
,
clients
.
L1Client
)
if
err
!=
nil
{
return
err
}
queueLength
,
err
:=
ctc
.
GetQueueLength
(
&
bind
.
CallOpts
{
BlockNumber
:
shutoffHeight
,
})
if
err
!=
nil
{
return
err
}
totalElements
,
err
:=
ctc
.
GetTotalElements
(
&
bind
.
CallOpts
{
BlockNumber
:
shutoffHeight
,
})
if
err
!=
nil
{
return
err
}
totalBatches
,
err
:=
ctc
.
GetTotalBatches
(
&
bind
.
CallOpts
{
BlockNumber
:
shutoffHeight
,
})
if
err
!=
nil
{
return
err
}
pending
,
err
:=
ctc
.
GetNumPendingQueueElements
(
&
bind
.
CallOpts
{
BlockNumber
:
shutoffHeight
,
})
if
err
!=
nil
{
return
err
}
log
.
Info
(
"CanonicalTransactionChain"
,
"address"
,
addresses
.
CanonicalTransactionChain
,
"queue-length"
,
queueLength
,
"total-elements"
,
totalElements
,
"total-batches"
,
totalBatches
,
"pending"
,
pending
,
)
blockNumber
,
err
:=
clients
.
L2Client
.
BlockNumber
(
context
.
Background
())
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Searching backwards for final deposit"
,
"start"
,
blockNumber
)
// Walk backards through the blocks until we find the final deposit.
for
{
bn
:=
new
(
big
.
Int
)
.
SetUint64
(
blockNumber
)
log
.
Info
(
"Checking L2 block"
,
"number"
,
bn
)
block
,
err
:=
clients
.
L2Client
.
BlockByNumber
(
context
.
Background
(),
bn
)
if
err
!=
nil
{
return
err
}
if
length
:=
len
(
block
.
Transactions
());
length
!=
1
{
return
fmt
.
Errorf
(
"unexpected number of transactions in block: %d"
,
length
)
}
tx
:=
block
.
Transactions
()[
0
]
hash
:=
tx
.
Hash
()
json
,
err
:=
legacyTransactionByHash
(
clients
.
L2RpcClient
,
hash
)
if
err
!=
nil
{
return
err
}
// If the queue origin is l1, then it is a deposit.
if
json
.
QueueOrigin
==
"l1"
{
if
json
.
QueueIndex
==
nil
{
// This should never happen.
return
fmt
.
Errorf
(
"queue index is nil for tx %s at height %d"
,
hash
.
Hex
(),
blockNumber
)
}
queueIndex
:=
uint64
(
*
json
.
QueueIndex
)
if
json
.
L1BlockNumber
==
nil
{
// This should never happen.
return
fmt
.
Errorf
(
"L1 block number is nil for tx %s at height %d"
,
hash
.
Hex
(),
blockNumber
)
}
l1BlockNumber
:=
json
.
L1BlockNumber
.
ToInt
()
log
.
Info
(
"Deposit found"
,
"l2-block"
,
blockNumber
,
"l1-block"
,
l1BlockNumber
,
"queue-index"
,
queueIndex
)
// This should never happen
if
json
.
L1BlockNumber
.
ToInt
()
.
Uint64
()
>
shutoffHeight
.
Uint64
()
{
log
.
Warn
(
"Lost deposit"
)
return
fmt
.
Errorf
(
"Lost deposit: %s"
,
hash
.
Hex
())
}
// Check to see if the final deposit was ingested. Subtract 1 here to handle zero
// indexing.
if
queueIndex
==
queueLength
.
Uint64
()
-
1
{
log
.
Info
(
"Found final deposit in l2geth"
,
"queue-index"
,
queueIndex
)
break
}
// If the queue index is less than the queue length, then not all deposits have
// been ingested by l2geth yet. This means that we need to reset the blocknumber
// to the latest block number to restart walking backwards to find deposits that
// have yet to be ingested.
if
queueIndex
<
queueLength
.
Uint64
()
{
log
.
Info
(
"Not all deposits ingested"
,
"queue-index"
,
queueIndex
,
"queue-length"
,
queueLength
.
Uint64
())
time
.
Sleep
(
time
.
Second
*
3
)
blockNumber
,
err
=
clients
.
L2Client
.
BlockNumber
(
context
.
Background
())
if
err
!=
nil
{
return
err
}
continue
}
// The queueIndex should never be greater than the queue length.
if
queueIndex
>
queueLength
.
Uint64
()
{
log
.
Warn
(
"Queue index is greater than queue length"
,
"queue-index"
,
queueIndex
,
"queue-length"
,
queueLength
.
Uint64
())
}
}
blockNumber
--
}
finalPending
,
err
:=
ctc
.
GetNumPendingQueueElements
(
&
bind
.
CallOpts
{})
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Remaining deposits that must be submitted"
,
"count"
,
finalPending
)
if
finalPending
.
Cmp
(
common
.
Big0
)
==
0
{
log
.
Info
(
"All deposits have been batch submitted"
)
}
return
nil
},
},
{
Name
:
"batches"
,
Usage
:
"Ensures that all batches have been submitted to L1"
,
Flags
:
flags
,
Action
:
func
(
cliCtx
*
cli
.
Context
)
error
{
clients
,
err
:=
util
.
NewClients
(
cliCtx
)
if
err
!=
nil
{
return
err
}
addresses
,
err
:=
util
.
NewAddresses
(
cliCtx
)
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Connecting to CanonicalTransactionChain"
,
"address"
,
addresses
.
CanonicalTransactionChain
)
ctc
,
err
:=
legacy_bindings
.
NewCanonicalTransactionChain
(
addresses
.
CanonicalTransactionChain
,
clients
.
L1Client
)
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Connecting to StateCommitmentChain"
,
"address"
,
addresses
.
StateCommitmentChain
)
scc
,
err
:=
legacy_bindings
.
NewStateCommitmentChain
(
addresses
.
StateCommitmentChain
,
clients
.
L1Client
)
if
err
!=
nil
{
return
err
}
var
wg
sync
.
WaitGroup
log
.
Info
(
"Waiting for CanonicalTransactionChain"
)
wg
.
Add
(
1
)
go
waitForTotalElements
(
&
wg
,
ctc
,
clients
.
L2Client
,
"CanonicalTransactionChain"
)
log
.
Info
(
"Waiting for StateCommitmentChain"
)
wg
.
Add
(
1
)
go
waitForTotalElements
(
&
wg
,
scc
,
clients
.
L2Client
,
"StateCommitmentChain"
)
wg
.
Wait
()
log
.
Info
(
"All batches have been submitted"
)
return
nil
},
},
}
if
err
:=
app
.
Run
(
os
.
Args
);
err
!=
nil
{
log
.
Crit
(
"Application failed"
,
"message"
,
err
)
}
}
// RollupContract represents a legacy rollup contract interface that
// exposes the GetTotalElements function. Both the StateCommitmentChain
// and the CanonicalTransactionChain implement this interface.
type
RollupContract
interface
{
GetTotalElements
(
opts
*
bind
.
CallOpts
)
(
*
big
.
Int
,
error
)
}
// waitForTotalElements will poll to see
func
waitForTotalElements
(
wg
*
sync
.
WaitGroup
,
contract
RollupContract
,
client
*
ethclient
.
Client
,
name
string
)
{
defer
wg
.
Done
()
for
{
bn
,
err
:=
client
.
BlockNumber
(
context
.
Background
())
if
err
!=
nil
{
log
.
Error
(
"cannot fetch blocknumber"
,
"error"
,
err
)
time
.
Sleep
(
3
*
time
.
Second
)
continue
}
totalElements
,
err
:=
contract
.
GetTotalElements
(
&
bind
.
CallOpts
{})
if
err
!=
nil
{
log
.
Error
(
"cannot fetch total elements"
,
"error"
,
err
)
time
.
Sleep
(
3
*
time
.
Second
)
continue
}
if
totalElements
.
Uint64
()
==
bn
{
log
.
Info
(
"Total elements matches block number"
,
"name"
,
name
,
"count"
,
bn
)
return
}
log
.
Info
(
"Waiting for elements to be submitted"
,
"name"
,
name
,
"count"
,
bn
-
totalElements
.
Uint64
(),
"height"
,
bn
,
"total-elements"
,
totalElements
.
Uint64
(),
)
time
.
Sleep
(
3
*
time
.
Second
)
}
}
// legacyTransactionByHash will fetch a transaction by hash and be sure to decode
// the additional fields added to legacy transactions.
func
legacyTransactionByHash
(
client
*
rpc
.
Client
,
hash
common
.
Hash
)
(
*
RPCTransaction
,
error
)
{
var
json
*
RPCTransaction
err
:=
client
.
CallContext
(
context
.
Background
(),
&
json
,
"eth_getTransactionByHash"
,
hash
)
if
err
!=
nil
{
return
nil
,
err
}
return
json
,
nil
}
// RPCTransaction represents a transaction that will serialize to the RPC representation of a
// transaction. This handles the extra legacy fields added to transactions.
type
RPCTransaction
struct
{
BlockHash
*
common
.
Hash
`json:"blockHash"`
BlockNumber
*
hexutil
.
Big
`json:"blockNumber"`
From
common
.
Address
`json:"from"`
Gas
hexutil
.
Uint64
`json:"gas"`
GasPrice
*
hexutil
.
Big
`json:"gasPrice"`
Hash
common
.
Hash
`json:"hash"`
Input
hexutil
.
Bytes
`json:"input"`
Nonce
hexutil
.
Uint64
`json:"nonce"`
To
*
common
.
Address
`json:"to"`
TransactionIndex
*
hexutil
.
Uint64
`json:"transactionIndex"`
Value
*
hexutil
.
Big
`json:"value"`
V
*
hexutil
.
Big
`json:"v"`
R
*
hexutil
.
Big
`json:"r"`
S
*
hexutil
.
Big
`json:"s"`
QueueOrigin
string
`json:"queueOrigin"`
L1TxOrigin
*
common
.
Address
`json:"l1TxOrigin"`
L1BlockNumber
*
hexutil
.
Big
`json:"l1BlockNumber"`
L1Timestamp
hexutil
.
Uint64
`json:"l1Timestamp"`
Index
*
hexutil
.
Uint64
`json:"index"`
QueueIndex
*
hexutil
.
Uint64
`json:"queueIndex"`
RawTransaction
hexutil
.
Bytes
`json:"rawTransaction"`
}
op-chain-ops/cmd/withdrawals/main.go
deleted
100644 → 0
View file @
cedeb4ec
package
main
import
(
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"math/rand"
"os"
"strings"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/params"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-node/rollup"
opservice
"github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
// abiTrue represents the storage representation of the boolean
// value true.
var
abiTrue
=
common
.
Hash
{
31
:
0x01
}
// batchSize represents the number of withdrawals to prove/finalize at a time.
var
batchSize
=
25
// callFrame represents the response returned from geth's
// `debug_traceTransaction` callTracer
type
callFrame
struct
{
Type
string
`json:"type"`
From
string
`json:"from"`
To
string
`json:"to,omitempty"`
Value
string
`json:"value,omitempty"`
Gas
string
`json:"gas"`
GasUsed
string
`json:"gasUsed"`
Input
string
`json:"input"`
Output
string
`json:"output,omitempty"`
Error
string
`json:"error,omitempty"`
Calls
[]
callFrame
`json:"calls,omitempty"`
}
// BigValue turns a 0x prefixed string into a `big.Int`
func
(
c
*
callFrame
)
BigValue
()
*
big
.
Int
{
v
:=
strings
.
TrimPrefix
(
c
.
Value
,
"0x"
)
b
,
_
:=
new
(
big
.
Int
)
.
SetString
(
v
,
16
)
return
b
}
// suspiciousWithdrawal represents a pending withdrawal that failed for some
// reason after the migration. These are written to disk so that they can
// be manually inspected.
type
suspiciousWithdrawal
struct
{
Withdrawal
*
crossdomain
.
Withdrawal
`json:"withdrawal"`
Legacy
*
crossdomain
.
LegacyWithdrawal
`json:"legacy"`
Trace
callFrame
`json:"trace"`
Index
int
`json:"index"`
Reason
string
`json:"reason"`
}
func
main
()
{
lvlHdlr
:=
log
.
StreamHandler
(
os
.
Stderr
,
log
.
TerminalFormat
(
isatty
.
IsTerminal
(
os
.
Stderr
.
Fd
())))
log
.
Root
()
.
SetHandler
(
log
.
LvlFilterHandler
(
log
.
LvlInfo
,
lvlHdlr
))
app
:=
&
cli
.
App
{
Name
:
"withdrawals"
,
Usage
:
"submits pending withdrawals"
,
Flags
:
[]
cli
.
Flag
{
&
cli
.
StringFlag
{
Name
:
"l1-rpc-url"
,
Value
:
"http://127.0.0.1:8545"
,
Usage
:
"RPC URL for an L1 Node"
,
},
&
cli
.
StringFlag
{
Name
:
"l2-rpc-url"
,
Value
:
"http://127.0.0.1:9545"
,
Usage
:
"RPC URL for an L2 Node"
,
},
&
cli
.
StringFlag
{
Name
:
"optimism-portal-address"
,
Usage
:
"Address of the OptimismPortal on L1"
,
},
&
cli
.
StringFlag
{
Name
:
"l1-crossdomain-messenger-address"
,
Usage
:
"Address of the L1CrossDomainMessenger"
,
},
&
cli
.
StringFlag
{
Name
:
"l1-standard-bridge-address"
,
Usage
:
"Address of the L1StandardBridge"
,
},
&
cli
.
StringFlag
{
Name
:
"ovm-messages"
,
Usage
:
"Path to ovm-messages.json"
,
},
&
cli
.
StringFlag
{
Name
:
"evm-messages"
,
Usage
:
"Path to evm-messages.json"
,
},
&
cli
.
StringFlag
{
Name
:
"witness-file"
,
Usage
:
"Path to l2geth witness file"
,
},
&
cli
.
StringFlag
{
Name
:
"private-key"
,
Usage
:
"Key to sign transactions with"
,
},
&
cli
.
StringFlag
{
Name
:
"bad-withdrawals-out"
,
Value
:
"bad-withdrawals.json"
,
Usage
:
"Path to write JSON file of bad withdrawals to manually inspect"
,
},
&
cli
.
StringFlag
{
Name
:
"storage-out"
,
Usage
:
"Path to write text file of L2ToL1MessagePasser storage"
,
},
},
Action
:
func
(
ctx
*
cli
.
Context
)
error
{
clients
,
err
:=
util
.
NewClients
(
ctx
)
if
err
!=
nil
{
return
err
}
// initialize the contract bindings
contracts
,
err
:=
newContracts
(
ctx
,
clients
.
L1Client
,
clients
.
L2Client
)
if
err
!=
nil
{
return
err
}
l1xdmAddr
:=
common
.
HexToAddress
(
ctx
.
String
(
"l1-crossdomain-messenger-address"
))
l1ChainID
,
err
:=
clients
.
L1Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
err
}
l2ChainID
,
err
:=
clients
.
L2Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
err
}
// create the set of withdrawals
wds
,
err
:=
newWithdrawals
(
ctx
,
l1ChainID
)
if
err
!=
nil
{
return
err
}
period
,
err
:=
contracts
.
L2OutputOracle
.
FINALIZATIONPERIODSECONDS
(
&
bind
.
CallOpts
{})
if
err
!=
nil
{
return
err
}
bedrockStartingBlockNumber
,
err
:=
contracts
.
L2OutputOracle
.
StartingBlockNumber
(
&
bind
.
CallOpts
{})
if
err
!=
nil
{
return
err
}
bedrockStartingBlock
,
err
:=
clients
.
L2Client
.
BlockByNumber
(
context
.
Background
(),
bedrockStartingBlockNumber
)
if
err
!=
nil
{
return
err
}
log
.
Info
(
"Withdrawal config"
,
"finalization-period"
,
period
,
"bedrock-starting-block-number"
,
bedrockStartingBlockNumber
,
"bedrock-starting-block-hash"
,
bedrockStartingBlock
.
Hash
()
.
Hex
())
if
!
bytes
.
Equal
(
bedrockStartingBlock
.
Extra
(),
genesis
.
BedrockTransitionBlockExtraData
)
{
return
errors
.
New
(
"genesis block mismatch"
)
}
outfile
:=
ctx
.
String
(
"bad-withdrawals-out"
)
f
,
err
:=
os
.
OpenFile
(
outfile
,
os
.
O_WRONLY
|
os
.
O_CREATE
|
os
.
O_APPEND
,
0
o644
)
if
err
!=
nil
{
return
err
}
defer
f
.
Close
()
// create a transactor
opts
,
err
:=
newTransactor
(
ctx
)
if
err
!=
nil
{
return
err
}
// Need this to compare in event parsing
l1StandardBridgeAddress
:=
common
.
HexToAddress
(
ctx
.
String
(
"l1-standard-bridge-address"
))
if
storageOutfile
:=
ctx
.
String
(
"storage-out"
);
storageOutfile
!=
""
{
ff
,
err
:=
os
.
OpenFile
(
storageOutfile
,
os
.
O_WRONLY
|
os
.
O_CREATE
|
os
.
O_APPEND
,
0
o644
)
if
err
!=
nil
{
return
err
}
defer
ff
.
Close
()
log
.
Info
(
"Fetching storage for L2ToL1MessagePasser"
)
if
storageRange
,
err
:=
callStorageRange
(
clients
,
predeploys
.
L2ToL1MessagePasserAddr
);
err
!=
nil
{
log
.
Info
(
"error getting storage range"
,
"err"
,
err
)
}
else
{
str
:=
""
for
key
,
value
:=
range
storageRange
{
str
+=
fmt
.
Sprintf
(
"%s: %s
\n
"
,
key
.
Hex
(),
value
.
Hex
())
}
_
,
err
=
ff
.
WriteString
(
str
)
if
err
!=
nil
{
return
err
}
}
}
nonce
,
err
:=
clients
.
L1Client
.
NonceAt
(
context
.
Background
(),
opts
.
From
,
nil
)
if
err
!=
nil
{
return
err
}
// The goroutines below use an atomic increment-and-get, so we need
// to subtract one here to make the initial value correct.
nonce
--
log
.
Info
(
"starting nonce"
,
"nonce"
,
nonce
)
proveWithdrawals
:=
func
(
wd
*
crossdomain
.
LegacyWithdrawal
,
bf
*
big
.
Int
,
i
int
)
{
// migrate the withdrawal
withdrawal
,
err
:=
crossdomain
.
MigrateWithdrawal
(
wd
,
&
l1xdmAddr
,
l2ChainID
)
if
err
!=
nil
{
log
.
Error
(
"error migrating withdrawal"
,
"err"
,
err
)
return
}
// Pass to Portal
hash
,
err
:=
withdrawal
.
Hash
()
if
err
!=
nil
{
log
.
Error
(
"error hashing withdrawal"
,
"err"
,
err
)
return
}
lcdm
:=
wd
.
CrossDomainMessage
()
legacyXdmHash
,
err
:=
lcdm
.
Hash
()
if
err
!=
nil
{
log
.
Error
(
"error hashing legacy withdrawal"
,
"err"
,
err
)
return
}
// check to see if the withdrawal has already been successfully
// relayed or received
isSuccess
,
err
:=
contracts
.
L1CrossDomainMessenger
.
SuccessfulMessages
(
&
bind
.
CallOpts
{},
legacyXdmHash
)
if
err
!=
nil
{
log
.
Error
(
"error checking legacy withdrawal status"
,
"err"
,
err
)
return
}
isFailed
,
err
:=
contracts
.
L1CrossDomainMessenger
.
FailedMessages
(
&
bind
.
CallOpts
{},
legacyXdmHash
)
if
err
!=
nil
{
log
.
Error
(
"error checking legacy withdrawal status"
,
"err"
,
err
)
return
}
xdmHash
:=
crypto
.
Keccak256Hash
(
withdrawal
.
Data
)
if
err
!=
nil
{
log
.
Error
(
"error hashing crossdomain message"
,
"err"
,
err
)
return
}
isSuccessNew
,
err
:=
contracts
.
L1CrossDomainMessenger
.
SuccessfulMessages
(
&
bind
.
CallOpts
{},
xdmHash
)
if
err
!=
nil
{
log
.
Error
(
"error checking withdrawal status"
,
"err"
,
err
)
return
}
isFailedNew
,
err
:=
contracts
.
L1CrossDomainMessenger
.
FailedMessages
(
&
bind
.
CallOpts
{},
xdmHash
)
if
err
!=
nil
{
log
.
Error
(
"error checking withdrawal status"
,
"err"
,
err
)
return
}
log
.
Info
(
"cross domain messenger status"
,
"hash"
,
legacyXdmHash
.
Hex
(),
"success"
,
isSuccess
,
"failed"
,
isFailed
,
"is-success-new"
,
isSuccessNew
,
"is-failed-new"
,
isFailedNew
)
// compute the storage slot
slot
,
err
:=
withdrawal
.
StorageSlot
()
if
err
!=
nil
{
log
.
Error
(
"error computing storage slot"
,
"err"
,
err
)
return
}
// successful messages can be skipped, received messages failed their execution and should be replayed
if
isSuccessNew
{
log
.
Info
(
"Message already relayed"
,
"index"
,
i
,
"hash"
,
hash
.
Hex
(),
"slot"
,
slot
.
Hex
())
return
}
// check the storage value of the slot to ensure that it is in
// the L2 storage. Without this check, the proof will fail
storageValue
,
err
:=
clients
.
L2Client
.
StorageAt
(
context
.
Background
(),
predeploys
.
L2ToL1MessagePasserAddr
,
slot
,
nil
)
if
err
!=
nil
{
log
.
Error
(
"error fetching storage slot value"
,
"err"
,
err
)
return
}
log
.
Debug
(
"L2ToL1MessagePasser status"
,
"value"
,
common
.
Bytes2Hex
(
storageValue
))
// the value should be set to a boolean in storage
if
!
bytes
.
Equal
(
storageValue
,
abiTrue
.
Bytes
())
{
log
.
Error
(
"storage slot not found in state"
,
"slot"
,
slot
.
Hex
(),
"xTarget"
,
wd
.
XDomainTarget
,
"xData"
,
wd
.
XDomainData
,
"xNonce"
,
wd
.
XDomainNonce
,
"xSender"
,
wd
.
XDomainSender
,
"sender"
,
wd
.
MessageSender
,
"success"
,
isSuccess
,
"failed"
,
isFailed
,
"failed-new"
,
isFailedNew
,
)
return
}
legacySlot
,
err
:=
wd
.
StorageSlot
()
if
err
!=
nil
{
log
.
Error
(
"error computing legacy storage slot"
,
"err"
,
err
)
return
}
legacyStorageValue
,
err
:=
clients
.
L2Client
.
StorageAt
(
context
.
Background
(),
predeploys
.
LegacyMessagePasserAddr
,
legacySlot
,
nil
)
if
err
!=
nil
{
log
.
Error
(
"error fetching legacy storage slot value"
,
"err"
,
err
)
return
}
log
.
Debug
(
"LegacyMessagePasser status"
,
"value"
,
common
.
Bytes2Hex
(
legacyStorageValue
))
// check to see if its already been proven
proven
,
err
:=
contracts
.
OptimismPortal
.
ProvenWithdrawals
(
&
bind
.
CallOpts
{},
hash
)
if
err
!=
nil
{
log
.
Error
(
"error fetching proven withdrawal status"
,
"err"
,
err
)
return
}
// if it has not been proven, then prove it
if
proven
.
Timestamp
.
Cmp
(
common
.
Big0
)
==
0
{
log
.
Info
(
"Proving withdrawal to OptimismPortal"
)
// create a transactor
optsCopy
,
err
:=
newTransactor
(
ctx
)
if
err
!=
nil
{
log
.
Crit
(
"error creating transactor"
,
"err"
,
err
)
return
}
optsCopy
.
Nonce
=
new
(
big
.
Int
)
.
SetUint64
(
atomic
.
AddUint64
(
&
nonce
,
1
))
optsCopy
.
GasTipCap
=
big
.
NewInt
(
2
_500_000_000
)
optsCopy
.
GasFeeCap
=
bf
if
err
:=
proveWithdrawalTransaction
(
contracts
,
clients
,
optsCopy
,
withdrawal
,
bedrockStartingBlockNumber
);
err
!=
nil
{
log
.
Error
(
"error proving withdrawal"
,
"err"
,
err
)
return
}
proven
,
err
=
contracts
.
OptimismPortal
.
ProvenWithdrawals
(
&
bind
.
CallOpts
{},
hash
)
if
err
!=
nil
{
log
.
Error
(
"error fetching proven withdrawal status"
,
"err"
,
err
)
return
}
if
proven
.
Timestamp
.
Cmp
(
common
.
Big0
)
==
0
{
log
.
Error
(
"error proving withdrawal"
,
"wdHash"
,
hash
)
}
}
else
{
log
.
Info
(
"Withdrawal already proven to OptimismPortal"
)
}
}
finalizeWithdrawals
:=
func
(
wd
*
crossdomain
.
LegacyWithdrawal
,
bf
*
big
.
Int
,
i
int
)
{
// migrate the withdrawal
withdrawal
,
err
:=
crossdomain
.
MigrateWithdrawal
(
wd
,
&
l1xdmAddr
,
l2ChainID
)
if
err
!=
nil
{
log
.
Error
(
"error migrating withdrawal"
,
"err"
,
err
)
return
}
// Pass to Portal
hash
,
err
:=
withdrawal
.
Hash
()
if
err
!=
nil
{
log
.
Error
(
"error hashing withdrawal"
,
"err"
,
err
)
return
}
lcdm
:=
wd
.
CrossDomainMessage
()
legacyXdmHash
,
err
:=
lcdm
.
Hash
()
if
err
!=
nil
{
log
.
Error
(
"error hashing legacy withdrawal"
,
"err"
,
err
)
return
}
// check to see if the withdrawal has already been successfully
// relayed or received
isSuccess
,
err
:=
contracts
.
L1CrossDomainMessenger
.
SuccessfulMessages
(
&
bind
.
CallOpts
{},
legacyXdmHash
)
if
err
!=
nil
{
log
.
Error
(
"error checking legacy withdrawal status"
,
"err"
,
err
)
return
}
xdmHash
:=
crypto
.
Keccak256Hash
(
withdrawal
.
Data
)
if
err
!=
nil
{
log
.
Error
(
"error hashing crossdomain message"
,
"err"
,
err
)
return
}
// check to see if its already been proven
proven
,
err
:=
contracts
.
OptimismPortal
.
ProvenWithdrawals
(
&
bind
.
CallOpts
{},
hash
)
if
err
!=
nil
{
log
.
Error
(
"error fetching proven withdrawal status"
,
"err"
,
err
)
return
}
// check to see if the withdrawal has been finalized already
isFinalized
,
err
:=
contracts
.
OptimismPortal
.
FinalizedWithdrawals
(
&
bind
.
CallOpts
{},
hash
)
if
err
!=
nil
{
log
.
Error
(
"error fetching finalized withdrawal status"
,
"err"
,
err
)
return
}
// Log an error if the withdrawal has not been proven
// It should have been proven in the previous loop
if
proven
.
Timestamp
.
Cmp
(
common
.
Big0
)
==
0
{
log
.
Error
(
"withdrawal has not been proven"
,
"wdHash"
,
hash
)
return
}
if
!
isFinalized
{
initialTime
:=
proven
.
Timestamp
.
Uint64
()
var
block
*
types
.
Block
for
{
log
.
Info
(
"Waiting for finalization"
)
block
,
err
=
clients
.
L1Client
.
BlockByNumber
(
context
.
Background
(),
nil
)
if
err
!=
nil
{
log
.
Error
(
"error fetching block"
,
"err"
,
err
)
}
if
block
.
Time
()
>=
initialTime
+
period
.
Uint64
()
{
log
.
Info
(
"can be finalized"
)
break
}
time
.
Sleep
(
1
*
time
.
Second
)
}
// Get the ETH balance of the withdrawal target *before* the finalization
targetBalBefore
,
err
:=
clients
.
L1Client
.
BalanceAt
(
context
.
Background
(),
wd
.
XDomainTarget
,
nil
)
if
err
!=
nil
{
log
.
Error
(
"error fetching target balance before"
,
"err"
,
err
)
return
}
log
.
Debug
(
"Balance before finalization"
,
"balance"
,
targetBalBefore
,
"account"
,
wd
.
XDomainTarget
)
log
.
Info
(
"Finalizing withdrawal"
)
// make a copy of opts
optsCopy
,
err
:=
newTransactor
(
ctx
)
if
err
!=
nil
{
log
.
Crit
(
"error creating transactor"
,
"err"
,
err
)
return
}
optsCopy
.
Nonce
=
new
(
big
.
Int
)
.
SetUint64
(
atomic
.
AddUint64
(
&
nonce
,
1
))
optsCopy
.
GasTipCap
=
big
.
NewInt
(
2
_500_000_000
)
optsCopy
.
GasFeeCap
=
bf
receipt
,
err
:=
finalizeWithdrawalTransaction
(
contracts
,
clients
,
optsCopy
,
wd
,
withdrawal
)
if
err
!=
nil
{
log
.
Error
(
"error finalizing withdrawal"
,
"err"
,
err
)
return
}
log
.
Info
(
"withdrawal finalized"
,
"tx-hash"
,
receipt
.
TxHash
,
"withdrawal-hash"
,
hash
)
finalizationTrace
,
err
:=
callTrace
(
clients
,
receipt
)
if
err
!=
nil
{
log
.
Error
(
"error fetching finalization trace"
,
"err"
,
err
)
return
}
isSuccessNewPost
,
err
:=
contracts
.
L1CrossDomainMessenger
.
SuccessfulMessages
(
&
bind
.
CallOpts
{},
xdmHash
)
if
err
!=
nil
{
log
.
Error
(
"error fetching new post success status"
,
"err"
,
err
)
return
}
// This would indicate that there is a replayability problem
if
isSuccess
&&
isSuccessNewPost
{
if
err
:=
writeSuspicious
(
f
,
withdrawal
,
wd
,
finalizationTrace
,
i
,
"should revert"
);
err
!=
nil
{
log
.
Error
(
"error writing suspicious withdrawal"
,
"err"
,
err
)
return
}
panic
(
"DOUBLE PLAYED DEPOSIT ALLOWED"
)
}
callFrame
:=
findWithdrawalCall
(
&
finalizationTrace
,
wd
,
l1xdmAddr
)
if
callFrame
==
nil
{
if
err
:=
writeSuspicious
(
f
,
withdrawal
,
wd
,
finalizationTrace
,
i
,
"cannot find callframe"
);
err
!=
nil
{
log
.
Error
(
"error writing suspicious withdrawal"
,
"err"
,
err
)
return
}
return
}
traceJson
,
err
:=
json
.
MarshalIndent
(
callFrame
,
""
,
" "
)
if
err
!=
nil
{
log
.
Error
(
"error marshalling callframe"
,
"err"
,
err
)
return
}
log
.
Debug
(
fmt
.
Sprintf
(
"%v"
,
string
(
traceJson
)))
abi
,
err
:=
bindings
.
L1StandardBridgeMetaData
.
GetAbi
()
if
err
!=
nil
{
log
.
Error
(
"error getting abi of the L1StandardBridge"
,
"err"
,
err
)
return
}
calldata
:=
hexutil
.
MustDecode
(
callFrame
.
Input
)
// this must be the L1 standard bridge
method
,
err
:=
abi
.
MethodById
(
calldata
)
// Handle L1StandardBridge specific logic
if
err
==
nil
{
args
,
err
:=
method
.
Inputs
.
Unpack
(
calldata
[
4
:
])
if
err
!=
nil
{
log
.
Error
(
"error unpacking calldata"
,
"err"
,
err
)
return
}
log
.
Info
(
"decoded calldata"
,
"name"
,
method
.
Name
)
switch
method
.
Name
{
case
"finalizeERC20Withdrawal"
:
if
err
:=
handleFinalizeERC20Withdrawal
(
args
,
receipt
,
l1StandardBridgeAddress
);
err
!=
nil
{
log
.
Error
(
"error handling finalizeERC20Withdrawal"
,
"err"
,
err
)
return
}
case
"finalizeETHWithdrawal"
:
if
err
:=
handleFinalizeETHWithdrawal
(
args
);
err
!=
nil
{
log
.
Error
(
"error handling finalizeETHWithdrawal"
,
"err"
,
err
)
return
}
default
:
log
.
Info
(
"Unhandled method"
,
"name"
,
method
.
Name
)
}
}
// Ensure that the target's balance was increasedData correctly
wdValue
,
err
:=
wd
.
Value
()
if
err
!=
nil
{
log
.
Error
(
"error getting withdrawal value"
,
"err"
,
err
)
return
}
if
method
!=
nil
{
log
.
Info
(
"withdrawal action"
,
"function"
,
method
.
Name
,
"value"
,
wdValue
)
}
else
{
log
.
Info
(
"unknown method"
,
"to"
,
wd
.
XDomainTarget
,
"data"
,
hexutil
.
Encode
(
wd
.
XDomainData
))
if
err
:=
writeSuspicious
(
f
,
withdrawal
,
wd
,
finalizationTrace
,
i
,
"unknown method"
);
err
!=
nil
{
log
.
Error
(
"error writing suspicious withdrawal"
,
"err"
,
err
)
return
}
}
// check that the user's intents are actually executed
if
common
.
HexToAddress
(
callFrame
.
To
)
!=
wd
.
XDomainTarget
{
log
.
Info
(
"target mismatch"
,
"index"
,
i
)
if
err
:=
writeSuspicious
(
f
,
withdrawal
,
wd
,
finalizationTrace
,
i
,
"target mismatch"
);
err
!=
nil
{
log
.
Error
(
"error writing suspicious withdrawal"
,
"err"
,
err
)
return
}
}
if
!
bytes
.
Equal
(
hexutil
.
MustDecode
(
callFrame
.
Input
),
wd
.
XDomainData
)
{
log
.
Info
(
"calldata mismatch"
,
"index"
,
i
)
if
err
:=
writeSuspicious
(
f
,
withdrawal
,
wd
,
finalizationTrace
,
i
,
"calldata mismatch"
);
err
!=
nil
{
log
.
Error
(
"error writing suspicious withdrawal"
,
"err"
,
err
)
return
}
return
}
if
callFrame
.
BigValue
()
.
Cmp
(
wdValue
)
!=
0
{
log
.
Info
(
"value mismatch"
,
"index"
,
i
)
if
err
:=
writeSuspicious
(
f
,
withdrawal
,
wd
,
finalizationTrace
,
i
,
"value mismatch"
);
err
!=
nil
{
log
.
Error
(
"error writing suspicious withdrawal"
,
"err"
,
err
)
return
}
return
}
// Get the ETH balance of the withdrawal target *after* the finalization
targetBalAfter
,
err
:=
clients
.
L1Client
.
BalanceAt
(
context
.
Background
(),
wd
.
XDomainTarget
,
nil
)
if
err
!=
nil
{
log
.
Error
(
"error getting target balance after"
,
"err"
,
err
)
return
}
diff
:=
new
(
big
.
Int
)
.
Sub
(
targetBalAfter
,
targetBalBefore
)
log
.
Debug
(
"balances"
,
"before"
,
targetBalBefore
,
"after"
,
targetBalAfter
,
"diff"
,
diff
)
isSuccessNewPost
,
err
=
contracts
.
L1CrossDomainMessenger
.
SuccessfulMessages
(
&
bind
.
CallOpts
{},
xdmHash
)
if
err
!=
nil
{
log
.
Error
(
"error getting success"
,
"err"
,
err
)
return
}
if
diff
.
Cmp
(
wdValue
)
!=
0
&&
isSuccessNewPost
&&
isSuccess
{
log
.
Info
(
"native eth balance diff mismatch"
,
"index"
,
i
,
"diff"
,
diff
,
"val"
,
wdValue
)
if
err
:=
writeSuspicious
(
f
,
withdrawal
,
wd
,
finalizationTrace
,
i
,
"balance mismatch"
);
err
!=
nil
{
log
.
Error
(
"error writing suspicious withdrawal"
,
"err"
,
err
)
return
}
return
}
}
else
{
log
.
Info
(
"Already finalized"
)
}
}
getBaseFee
:=
func
()
(
*
big
.
Int
,
error
)
{
block
,
err
:=
clients
.
L1Client
.
BlockByNumber
(
context
.
Background
(),
nil
)
if
err
!=
nil
{
return
nil
,
err
}
baseFee
:=
misc
.
CalcBaseFee
(
params
.
MainnetChainConfig
,
block
.
Header
())
baseFee
=
baseFee
.
Add
(
baseFee
,
big
.
NewInt
(
10
_000_000_000
))
return
baseFee
,
nil
}
batchTxs
:=
func
(
cb
func
(
*
crossdomain
.
LegacyWithdrawal
,
*
big
.
Int
,
int
))
error
{
sem
:=
make
(
chan
struct
{},
batchSize
)
var
bf
*
big
.
Int
var
err
error
for
i
,
wd
:=
range
wds
{
if
i
==
0
||
i
%
batchSize
==
0
{
bf
,
err
=
getBaseFee
()
if
err
!=
nil
{
return
err
}
}
if
i
%
5
==
0
{
log
.
Info
(
"kicking off batch transaction"
,
"i"
,
i
,
"len"
,
len
(
wds
))
}
sem
<-
struct
{}{}
go
func
(
wd
*
crossdomain
.
LegacyWithdrawal
,
bf
*
big
.
Int
,
i
int
)
{
defer
func
()
{
<-
sem
}()
cb
(
wd
,
bf
,
i
)
// Avoid hammering Cloudflare/our infrastructure too much
time
.
Sleep
(
50
*
time
.
Millisecond
+
time
.
Duration
(
rand
.
Intn
(
100
))
*
time
.
Millisecond
)
}(
wd
,
bf
,
i
)
}
return
nil
}
if
err
:=
batchTxs
(
proveWithdrawals
);
err
!=
nil
{
return
err
}
// Now that all of the withdrawals have been proven, we can finalize them.
// Note that we assume that the finalization period is low enough that
// we can finalize all of the withdrawals shortly after they have been proven.
log
.
Info
(
"All withdrawals have been proven! Moving on to finalization."
)
// Loop through withdrawals (`batchSize` wds at a time) and finalize each batch in parallel.
if
err
:=
batchTxs
(
finalizeWithdrawals
);
err
!=
nil
{
return
err
}
return
nil
},
}
if
err
:=
app
.
Run
(
os
.
Args
);
err
!=
nil
{
log
.
Crit
(
"error in migration"
,
"err"
,
err
)
}
}
// callTrace will call `debug_traceTransaction` on a remote node
func
callTrace
(
c
*
util
.
Clients
,
receipt
*
types
.
Receipt
)
(
callFrame
,
error
)
{
var
finalizationTrace
callFrame
tracer
:=
"callTracer"
traceConfig
:=
tracers
.
TraceConfig
{
Tracer
:
&
tracer
,
}
err
:=
c
.
L1RpcClient
.
Call
(
&
finalizationTrace
,
"debug_traceTransaction"
,
receipt
.
TxHash
,
traceConfig
)
return
finalizationTrace
,
err
}
func
callStorageRangeAt
(
client
*
rpc
.
Client
,
blockHash
common
.
Hash
,
txIndex
int
,
addr
common
.
Address
,
keyStart
hexutil
.
Bytes
,
maxResult
int
,
)
(
*
eth
.
StorageRangeResult
,
error
)
{
var
storageRange
*
eth
.
StorageRangeResult
err
:=
client
.
Call
(
&
storageRange
,
"debug_storageRangeAt"
,
blockHash
,
txIndex
,
addr
,
keyStart
,
maxResult
)
return
storageRange
,
err
}
func
callStorageRange
(
c
*
util
.
Clients
,
addr
common
.
Address
)
(
state
.
Storage
,
error
)
{
header
,
err
:=
c
.
L2Client
.
HeaderByNumber
(
context
.
Background
(),
nil
)
if
err
!=
nil
{
return
nil
,
err
}
hash
:=
header
.
Hash
()
keyStart
:=
hexutil
.
Bytes
(
common
.
Hash
{}
.
Bytes
())
maxResult
:=
1000
ret
:=
make
(
state
.
Storage
)
for
{
result
,
err
:=
callStorageRangeAt
(
c
.
L2RpcClient
,
hash
,
0
,
addr
,
keyStart
,
maxResult
)
if
err
!=
nil
{
return
nil
,
err
}
for
key
,
value
:=
range
result
.
Storage
{
ret
[
key
]
=
value
.
Value
}
if
result
.
NextKey
==
nil
{
break
}
else
{
keyStart
=
hexutil
.
Bytes
(
result
.
NextKey
.
Bytes
())
}
}
return
ret
,
nil
}
// handleFinalizeETHWithdrawal will ensure that the calldata is correct
func
handleFinalizeETHWithdrawal
(
args
[]
any
)
error
{
from
,
ok
:=
args
[
0
]
.
(
common
.
Address
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid type: from"
)
}
to
,
ok
:=
args
[
1
]
.
(
common
.
Address
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid type: to"
)
}
amount
,
ok
:=
args
[
2
]
.
(
*
big
.
Int
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid type: amount"
)
}
extraData
,
ok
:=
args
[
3
]
.
([]
byte
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid type: extraData"
)
}
log
.
Info
(
"decoded calldata"
,
"from"
,
from
,
"to"
,
to
,
"amount"
,
amount
,
"extraData"
,
extraData
,
)
return
nil
}
// handleFinalizeERC20Withdrawal will look at the receipt logs and make
// assertions that the values are correct
func
handleFinalizeERC20Withdrawal
(
args
[]
any
,
receipt
*
types
.
Receipt
,
l1StandardBridgeAddress
common
.
Address
)
error
{
erc20Abi
,
err
:=
bindings
.
ERC20MetaData
.
GetAbi
()
if
err
!=
nil
{
return
err
}
transferEvent
:=
erc20Abi
.
Events
[
"Transfer"
]
// Handle logic for ERC20 withdrawals
l1Token
,
ok
:=
args
[
0
]
.
(
common
.
Address
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid abi"
)
}
l2Token
,
ok
:=
args
[
1
]
.
(
common
.
Address
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid abi"
)
}
from
,
ok
:=
args
[
2
]
.
(
common
.
Address
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid abi"
)
}
to
,
ok
:=
args
[
3
]
.
(
common
.
Address
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid abi"
)
}
amount
,
ok
:=
args
[
4
]
.
(
*
big
.
Int
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid abi"
)
}
extraData
,
ok
:=
args
[
5
]
.
([]
byte
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid abi"
)
}
log
.
Info
(
"decoded calldata"
,
"l1Token"
,
l1Token
,
"l2Token"
,
l2Token
,
"from"
,
from
,
"to"
,
to
,
"amount"
,
amount
,
"extraData"
,
extraData
,
)
// Look for the ERC20 token transfer topic
for
_
,
l
:=
range
receipt
.
Logs
{
topic
:=
l
.
Topics
[
0
]
if
topic
==
transferEvent
.
ID
{
if
l
.
Address
==
l1Token
{
a
,
_
:=
transferEvent
.
Inputs
.
Unpack
(
l
.
Data
)
if
len
(
l
.
Topics
)
<
3
{
return
fmt
.
Errorf
(
""
)
}
_from
:=
common
.
BytesToAddress
(
l
.
Topics
[
1
]
.
Bytes
())
_to
:=
common
.
BytesToAddress
(
l
.
Topics
[
2
]
.
Bytes
())
// from the L1StandardBridge
if
_from
!=
l1StandardBridgeAddress
{
return
fmt
.
Errorf
(
"from mismatch: %x - %x"
,
_from
,
l1StandardBridgeAddress
)
}
if
to
!=
_to
{
return
fmt
.
Errorf
(
"to mismatch: %x - %x"
,
to
,
_to
)
}
_amount
,
ok
:=
a
[
0
]
.
(
*
big
.
Int
)
if
!
ok
{
return
fmt
.
Errorf
(
"invalid abi in transfer event"
)
}
if
amount
.
Cmp
(
_amount
)
!=
0
{
return
fmt
.
Errorf
(
"amount mismatch: %d - %d"
,
amount
,
_amount
)
}
}
}
}
return
nil
}
// proveWithdrawalTransaction will build the data required for proving a
// withdrawal and then send the transaction and make sure that it is included
// and successful and then wait for the finalization period to elapse.
func
proveWithdrawalTransaction
(
c
*
contracts
,
cl
*
util
.
Clients
,
opts
*
bind
.
TransactOpts
,
withdrawal
*
crossdomain
.
Withdrawal
,
bn
*
big
.
Int
)
error
{
l2OutputIndex
,
outputRootProof
,
trieNodes
,
err
:=
createOutput
(
withdrawal
,
c
.
L2OutputOracle
,
bn
,
cl
)
if
err
!=
nil
{
return
err
}
hash
,
err
:=
withdrawal
.
Hash
()
if
err
!=
nil
{
return
err
}
wdTx
:=
withdrawal
.
WithdrawalTransaction
()
tx
,
err
:=
c
.
OptimismPortal
.
ProveWithdrawalTransaction
(
opts
,
wdTx
,
l2OutputIndex
,
outputRootProof
,
trieNodes
,
)
if
err
!=
nil
{
return
err
}
log
.
Info
(
"proving withdrawal"
,
"tx-hash"
,
tx
.
Hash
(),
"nonce"
,
tx
.
Nonce
())
receipt
,
err
:=
bind
.
WaitMined
(
context
.
Background
(),
cl
.
L1Client
,
tx
)
if
err
!=
nil
{
return
err
}
if
receipt
.
Status
!=
types
.
ReceiptStatusSuccessful
{
return
errors
.
New
(
"withdrawal proof unsuccessful"
)
}
log
.
Info
(
"withdrawal proved"
,
"tx-hash"
,
tx
.
Hash
(),
"withdrawal-hash"
,
hash
)
return
nil
}
func
finalizeWithdrawalTransaction
(
c
*
contracts
,
cl
*
util
.
Clients
,
opts
*
bind
.
TransactOpts
,
wd
*
crossdomain
.
LegacyWithdrawal
,
withdrawal
*
crossdomain
.
Withdrawal
,
)
(
*
types
.
Receipt
,
error
)
{
if
wd
.
XDomainTarget
==
(
common
.
Address
{})
{
log
.
Warn
(
"nil withdrawal target"
,
"xTarget"
,
wd
.
XDomainTarget
,
"xData"
,
wd
.
XDomainData
,
"xNonce"
,
wd
.
XDomainNonce
,
"xSender"
,
wd
.
XDomainSender
,
"sender"
,
wd
.
MessageSender
,
)
return
nil
,
errors
.
New
(
"withdrawal target is nil, should never happen"
)
}
wdTx
:=
withdrawal
.
WithdrawalTransaction
()
// Finalize withdrawal
tx
,
err
:=
c
.
OptimismPortal
.
FinalizeWithdrawalTransaction
(
opts
,
wdTx
,
)
if
err
!=
nil
{
return
nil
,
err
}
receipt
,
err
:=
bind
.
WaitMined
(
context
.
Background
(),
cl
.
L1Client
,
tx
)
if
err
!=
nil
{
return
nil
,
err
}
if
receipt
.
Status
!=
types
.
ReceiptStatusSuccessful
{
return
nil
,
errors
.
New
(
"withdrawal finalize unsuccessful"
)
}
return
receipt
,
nil
}
// contracts represents a set of bound contracts
type
contracts
struct
{
OptimismPortal
*
bindings
.
OptimismPortal
L1CrossDomainMessenger
*
bindings
.
L1CrossDomainMessenger
L2OutputOracle
*
bindings
.
L2OutputOracle
}
// newContracts will create a contracts struct with the contract bindings
// preconfigured
func
newContracts
(
ctx
*
cli
.
Context
,
l1Backend
,
l2Backend
bind
.
ContractBackend
)
(
*
contracts
,
error
)
{
optimismPortalAddr
,
err
:=
opservice
.
ParseAddress
(
ctx
.
String
(
"optimism-portal-address"
))
if
err
!=
nil
{
return
nil
,
errors
.
New
(
"OptimismPortal address not configured"
)
}
portal
,
err
:=
bindings
.
NewOptimismPortal
(
optimismPortalAddr
,
l1Backend
)
if
err
!=
nil
{
return
nil
,
err
}
l1xdmAddr
,
err
:=
opservice
.
ParseAddress
(
ctx
.
String
(
"l1-crossdomain-messenger-address"
))
if
err
!=
nil
{
return
nil
,
errors
.
New
(
"L1CrossDomainMessenger address not configured"
)
}
l1CrossDomainMessenger
,
err
:=
bindings
.
NewL1CrossDomainMessenger
(
l1xdmAddr
,
l1Backend
)
if
err
!=
nil
{
return
nil
,
err
}
l2OracleAddr
,
err
:=
portal
.
L2ORACLE
(
&
bind
.
CallOpts
{})
if
err
!=
nil
{
return
nil
,
err
}
oracle
,
err
:=
bindings
.
NewL2OutputOracle
(
l2OracleAddr
,
l1Backend
)
if
err
!=
nil
{
return
nil
,
err
}
log
.
Info
(
"Addresses"
,
"l1-crossdomain-messenger"
,
l1xdmAddr
,
"optimism-portal"
,
optimismPortalAddr
,
"l2-output-oracle"
,
l2OracleAddr
,
)
return
&
contracts
{
OptimismPortal
:
portal
,
L1CrossDomainMessenger
:
l1CrossDomainMessenger
,
L2OutputOracle
:
oracle
,
},
nil
}
// newWithdrawals will create a set of legacy withdrawals
func
newWithdrawals
(
ctx
*
cli
.
Context
,
l1ChainID
*
big
.
Int
)
([]
*
crossdomain
.
LegacyWithdrawal
,
error
)
{
ovmMsgs
:=
ctx
.
String
(
"ovm-messages"
)
evmMsgs
:=
ctx
.
String
(
"evm-messages"
)
witnessFile
:=
ctx
.
String
(
"witness-file"
)
log
.
Debug
(
"Migration data"
,
"ovm-path"
,
ovmMsgs
,
"evm-messages"
,
evmMsgs
,
"witness-file"
,
witnessFile
)
var
ovmMessages
[]
*
crossdomain
.
SentMessage
var
err
error
if
ovmMsgs
!=
""
{
ovmMessages
,
err
=
crossdomain
.
NewSentMessageFromJSON
(
ovmMsgs
)
if
err
!=
nil
{
return
nil
,
err
}
}
// use empty ovmMessages if its not mainnet. The mainnet messages are
// committed to in git.
if
l1ChainID
.
Cmp
(
common
.
Big1
)
!=
0
{
log
.
Info
(
"not using ovm messages because its not mainnet"
)
ovmMessages
=
[]
*
crossdomain
.
SentMessage
{}
}
var
evmMessages
[]
*
crossdomain
.
SentMessage
if
witnessFile
!=
""
{
evmMessages
,
_
,
err
=
crossdomain
.
ReadWitnessData
(
witnessFile
)
if
err
!=
nil
{
return
nil
,
err
}
}
else
if
evmMsgs
!=
""
{
evmMessages
,
err
=
crossdomain
.
NewSentMessageFromJSON
(
evmMsgs
)
if
err
!=
nil
{
return
nil
,
err
}
}
else
{
return
nil
,
errors
.
New
(
"must provide either witness file or evm messages"
)
}
migrationData
:=
crossdomain
.
MigrationData
{
OvmMessages
:
ovmMessages
,
EvmMessages
:
evmMessages
,
}
wds
,
_
,
err
:=
migrationData
.
ToWithdrawals
()
if
err
!=
nil
{
return
nil
,
err
}
if
len
(
wds
)
==
0
{
return
nil
,
errors
.
New
(
"no withdrawals"
)
}
log
.
Info
(
"Converted migration data to withdrawals successfully"
,
"count"
,
len
(
wds
))
return
wds
,
nil
}
// newTransactor creates a new transact context given a cli context
func
newTransactor
(
ctx
*
cli
.
Context
)
(
*
bind
.
TransactOpts
,
error
)
{
if
ctx
.
String
(
"private-key"
)
==
""
{
return
nil
,
errors
.
New
(
"No private key to transact with"
)
}
privateKey
,
err
:=
crypto
.
HexToECDSA
(
strings
.
TrimPrefix
(
ctx
.
String
(
"private-key"
),
"0x"
))
if
err
!=
nil
{
return
nil
,
err
}
l1RpcURL
:=
ctx
.
String
(
"l1-rpc-url"
)
l1Client
,
err
:=
ethclient
.
Dial
(
l1RpcURL
)
if
err
!=
nil
{
return
nil
,
err
}
l1ChainID
,
err
:=
l1Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
nil
,
err
}
opts
,
err
:=
bind
.
NewKeyedTransactorWithChainID
(
privateKey
,
l1ChainID
)
if
err
!=
nil
{
return
nil
,
err
}
return
opts
,
nil
}
// findWithdrawalCall will find the call frame for the call that
// represents the user's intent.
func
findWithdrawalCall
(
trace
*
callFrame
,
wd
*
crossdomain
.
LegacyWithdrawal
,
l1xdm
common
.
Address
)
*
callFrame
{
isCall
:=
trace
.
Type
==
"CALL"
isTarget
:=
common
.
HexToAddress
(
trace
.
To
)
==
wd
.
XDomainTarget
isFrom
:=
common
.
HexToAddress
(
trace
.
From
)
==
l1xdm
if
isCall
&&
isTarget
&&
isFrom
{
return
trace
}
for
_
,
subcall
:=
range
trace
.
Calls
{
if
call
:=
findWithdrawalCall
(
&
subcall
,
wd
,
l1xdm
);
call
!=
nil
{
return
call
}
}
return
nil
}
// createOutput will create the data required to send a withdrawal transaction.
func
createOutput
(
withdrawal
*
crossdomain
.
Withdrawal
,
oracle
*
bindings
.
L2OutputOracle
,
blockNumber
*
big
.
Int
,
clients
*
util
.
Clients
,
)
(
*
big
.
Int
,
bindings
.
TypesOutputRootProof
,
[][]
byte
,
error
)
{
// compute the storage slot that the withdrawal is stored in
slot
,
err
:=
withdrawal
.
StorageSlot
()
if
err
!=
nil
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
err
}
// find the output index that the withdrawal was committed to in
l2OutputIndex
,
err
:=
oracle
.
GetL2OutputIndexAfter
(
&
bind
.
CallOpts
{},
blockNumber
)
if
err
!=
nil
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
err
}
// fetch the output the commits to the withdrawal using the index
l2Output
,
err
:=
oracle
.
GetL2Output
(
&
bind
.
CallOpts
{},
l2OutputIndex
)
if
err
!=
nil
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
err
}
log
.
Debug
(
"L2 output"
,
"index"
,
l2OutputIndex
,
"root"
,
common
.
Bytes2Hex
(
l2Output
.
OutputRoot
[
:
]),
"l2-blocknumber"
,
l2Output
.
L2BlockNumber
,
"timestamp"
,
l2Output
.
Timestamp
,
)
// get the block header committed to in the output
header
,
err
:=
clients
.
L2Client
.
HeaderByNumber
(
context
.
Background
(),
l2Output
.
L2BlockNumber
)
if
err
!=
nil
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
err
}
// get the storage proof for the withdrawal's storage slot
proof
,
err
:=
clients
.
L2GethClient
.
GetProof
(
context
.
Background
(),
predeploys
.
L2ToL1MessagePasserAddr
,
[]
string
{
slot
.
String
()},
blockNumber
)
if
err
!=
nil
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
err
}
if
count
:=
len
(
proof
.
StorageProof
);
count
!=
1
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
fmt
.
Errorf
(
"invalid amount of storage proofs: %d"
,
count
)
}
trieNodes
:=
make
([][]
byte
,
len
(
proof
.
StorageProof
[
0
]
.
Proof
))
for
i
,
s
:=
range
proof
.
StorageProof
[
0
]
.
Proof
{
trieNodes
[
i
]
=
common
.
FromHex
(
s
)
}
// create an output root proof
outputRootProof
:=
bindings
.
TypesOutputRootProof
{
Version
:
[
32
]
byte
{},
StateRoot
:
header
.
Root
,
MessagePasserStorageRoot
:
proof
.
StorageHash
,
LatestBlockhash
:
header
.
Hash
(),
}
// Compute the output root locally
l2OutputRoot
,
err
:=
rollup
.
ComputeL2OutputRoot
(
&
outputRootProof
)
localOutputRootHash
:=
common
.
Hash
(
l2OutputRoot
)
if
err
!=
nil
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
err
}
// ensure that the locally computed hash matches
if
l2Output
.
OutputRoot
!=
localOutputRootHash
{
return
nil
,
bindings
.
TypesOutputRootProof
{},
nil
,
fmt
.
Errorf
(
"mismatch in output root hashes, got 0x%x expected 0x%x"
,
localOutputRootHash
,
l2Output
.
OutputRoot
)
}
log
.
Info
(
"output root proof"
,
"version"
,
common
.
Hash
(
outputRootProof
.
Version
),
"state-root"
,
common
.
Hash
(
outputRootProof
.
StateRoot
),
"storage-root"
,
common
.
Hash
(
outputRootProof
.
MessagePasserStorageRoot
),
"block-hash"
,
common
.
Hash
(
outputRootProof
.
LatestBlockhash
),
"trie-node-count"
,
len
(
trieNodes
),
)
return
l2OutputIndex
,
outputRootProof
,
trieNodes
,
nil
}
// writeSuspicious will create a suspiciousWithdrawal and then append it to a
// JSONL file. Each line is its own JSON where there is a newline separating them.
func
writeSuspicious
(
f
*
os
.
File
,
withdrawal
*
crossdomain
.
Withdrawal
,
wd
*
crossdomain
.
LegacyWithdrawal
,
finalizationTrace
callFrame
,
i
int
,
reason
string
,
)
error
{
bad
:=
suspiciousWithdrawal
{
Withdrawal
:
withdrawal
,
Legacy
:
wd
,
Trace
:
finalizationTrace
,
Index
:
i
,
Reason
:
reason
,
}
data
,
err
:=
json
.
Marshal
(
bad
)
if
err
!=
nil
{
return
err
}
_
,
err
=
f
.
WriteString
(
string
(
data
)
+
"
\n
"
)
return
err
}
op-chain-ops/crossdomain/migrate.go
View file @
dfee56f5
package
crossdomain
package
crossdomain
import
(
import
(
"errors"
"fmt"
"fmt"
"math/big"
"math/big"
...
@@ -9,16 +8,9 @@ import (
...
@@ -9,16 +8,9 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params"
)
)
var
(
abiTrue
=
common
.
Hash
{
31
:
0x01
}
errLegacyStorageSlotNotFound
=
errors
.
New
(
"cannot find storage slot"
)
)
// Constants used by `CrossDomainMessenger.baseGas`
// Constants used by `CrossDomainMessenger.baseGas`
var
(
var
(
RelayConstantOverhead
uint64
=
200
_000
RelayConstantOverhead
uint64
=
200
_000
...
@@ -30,43 +22,6 @@ var (
...
@@ -30,43 +22,6 @@ var (
RelayGasCheckBuffer
uint64
=
5
_000
RelayGasCheckBuffer
uint64
=
5
_000
)
)
// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB.
func
MigrateWithdrawals
(
withdrawals
SafeFilteredWithdrawals
,
db
vm
.
StateDB
,
l1CrossDomainMessenger
*
common
.
Address
,
noCheck
bool
,
chainID
*
big
.
Int
,
)
error
{
for
i
,
legacy
:=
range
withdrawals
{
legacySlot
,
err
:=
legacy
.
StorageSlot
()
if
err
!=
nil
{
return
err
}
if
!
noCheck
{
legacyValue
:=
db
.
GetState
(
predeploys
.
LegacyMessagePasserAddr
,
legacySlot
)
if
legacyValue
!=
abiTrue
{
return
fmt
.
Errorf
(
"%w: %s"
,
errLegacyStorageSlotNotFound
,
legacySlot
)
}
}
withdrawal
,
err
:=
MigrateWithdrawal
(
legacy
,
l1CrossDomainMessenger
,
chainID
)
if
err
!=
nil
{
return
err
}
slot
,
err
:=
withdrawal
.
StorageSlot
()
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot compute withdrawal storage slot: %w"
,
err
)
}
db
.
SetState
(
predeploys
.
L2ToL1MessagePasserAddr
,
slot
,
abiTrue
)
log
.
Info
(
"Migrated withdrawal"
,
"number"
,
i
,
"slot"
,
slot
)
}
return
nil
}
// MigrateWithdrawal will turn a LegacyWithdrawal into a bedrock
// MigrateWithdrawal will turn a LegacyWithdrawal into a bedrock
// style Withdrawal.
// style Withdrawal.
func
MigrateWithdrawal
(
func
MigrateWithdrawal
(
...
...
op-chain-ops/crossdomain/params.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain
import
(
"math/big"
)
// Params contains the configuration parameters used for verifying
// the integrity of the migration.
type
Params
struct
{
// ExpectedSupplyDelta is the expected delta between the total supply of OVM ETH,
// and ETH we were able to migrate. This is used to account for supply bugs in
//previous regenesis events.
ExpectedSupplyDelta
*
big
.
Int
}
var
ParamsByChainID
=
map
[
int
]
*
Params
{
1
:
{
// Regenesis 4 (Nov 11 2021) contained a supply bug such that the total OVM ETH
// supply was 1.628470012 ETH greater than the sum balance of every account migrated
// / during the regenesis. A further 0.0012 ETH was incorrectly not removed from the
// total supply by accidental invocations of the Saurik bug (https://www.saurik.com/optimism.html).
new
(
big
.
Int
)
.
SetUint64
(
1627270011999999992
),
},
5
:
{
new
(
big
.
Int
),
},
}
op-chain-ops/crossdomain/precheck.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain
import
(
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log"
)
var
(
ErrUnknownSlotInMessagePasser
=
errors
.
New
(
"unknown slot in legacy message passer"
)
ErrMissingSlotInWitness
=
errors
.
New
(
"missing storage slot in witness data (see logs for details)"
)
)
// PreCheckWithdrawals checks that the given list of withdrawals represents all withdrawals made
// in the legacy system and filters out any extra withdrawals not included in the legacy system.
func
PreCheckWithdrawals
(
db
*
state
.
StateDB
,
withdrawals
DangerousUnfilteredWithdrawals
,
invalidMessages
[]
InvalidMessage
)
(
SafeFilteredWithdrawals
,
error
)
{
// Convert each withdrawal into a storage slot, and build a map of those slots.
validSlotsInp
:=
make
(
map
[
common
.
Hash
]
*
LegacyWithdrawal
)
for
_
,
wd
:=
range
withdrawals
{
slot
,
err
:=
wd
.
StorageSlot
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot check withdrawals: %w"
,
err
)
}
validSlotsInp
[
slot
]
=
wd
}
// Convert each invalid message into a storage slot, and build a map of those slots.
invalidSlotsInp
:=
make
(
map
[
common
.
Hash
]
InvalidMessage
)
for
_
,
msg
:=
range
invalidMessages
{
slot
,
err
:=
msg
.
StorageSlot
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot check invalid messages: %w"
,
err
)
}
invalidSlotsInp
[
slot
]
=
msg
}
// Build a mapping of the slots of all messages actually sent in the legacy system.
var
count
int
var
innerErr
error
slotsAct
:=
make
(
map
[
common
.
Hash
]
bool
)
progress
:=
util
.
ProgressLogger
(
1000
,
"Iterating legacy messages"
)
err
:=
db
.
ForEachStorage
(
predeploys
.
LegacyMessagePasserAddr
,
func
(
key
,
value
common
.
Hash
)
bool
{
progress
()
// When a message is inserted into the LegacyMessagePasser, it is stored with the value
// of the ABI encoding of "true". Although there should not be any other storage slots, we
// can safely ignore anything that is not "true".
if
value
!=
abiTrue
{
// Should not happen!
innerErr
=
fmt
.
Errorf
(
"%w: key: %s, val: %s"
,
ErrUnknownSlotInMessagePasser
,
key
.
String
(),
value
.
String
())
return
true
}
// Slot exists, so add it to the map.
slotsAct
[
key
]
=
true
count
++
return
true
})
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot iterate over LegacyMessagePasser: %w"
,
err
)
}
if
innerErr
!=
nil
{
return
nil
,
innerErr
}
// Log the number of messages we found.
log
.
Info
(
"Iterated legacy messages"
,
"count"
,
count
)
// Iterate over the list of actual slots and check that we have an input message for each one.
var
missing
int
for
slot
:=
range
slotsAct
{
_
,
okValid
:=
validSlotsInp
[
slot
]
_
,
okInvalid
:=
invalidSlotsInp
[
slot
]
if
!
okValid
&&
!
okInvalid
{
log
.
Error
(
"missing storage slot"
,
"slot"
,
slot
.
String
())
missing
++
}
}
if
missing
>
0
{
log
.
Error
(
"missing storage slots in witness data"
,
"count"
,
missing
)
return
nil
,
ErrMissingSlotInWitness
}
// Iterate over the list of input messages and check that we have a known slot for each one.
// We'll filter out any extra messages that are not in the legacy system.
filtered
:=
make
(
SafeFilteredWithdrawals
,
0
)
for
slot
:=
range
validSlotsInp
{
_
,
ok
:=
slotsAct
[
slot
]
if
!
ok
{
log
.
Info
(
"filtering out unknown input message"
,
"slot"
,
slot
.
String
())
continue
}
wd
:=
validSlotsInp
[
slot
]
if
wd
.
MessageSender
!=
predeploys
.
L2CrossDomainMessengerAddr
{
log
.
Info
(
"filtering out message from sender other than the L2XDM"
,
"sender"
,
wd
.
MessageSender
)
continue
}
filtered
=
append
(
filtered
,
wd
)
}
// At this point, we know that the list of filtered withdrawals MUST be exactly the same as the
// list of withdrawals in the state. If we didn't have enough withdrawals, we would've errored
// out, and if we had too many, we would've filtered them out.
return
filtered
,
nil
}
op-chain-ops/crossdomain/precheck_test.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain
import
(
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/require"
)
func
TestPreCheckWithdrawals_Filtering
(
t
*
testing
.
T
)
{
dbWds
:=
[]
*
LegacyWithdrawal
{
// Random legacy WD to something other than the L2XDM.
{
MessageSender
:
common
.
Address
{
19
:
0xFF
},
XDomainTarget
:
common
.
Address
{
19
:
0x01
},
XDomainSender
:
common
.
Address
{
19
:
0x02
},
XDomainData
:
[]
byte
{
0x01
,
0x02
,
0x03
},
XDomainNonce
:
big
.
NewInt
(
0
),
},
// Random legacy WD to the L2XDM. Should be the only thing
// returned by the prechecker.
{
MessageSender
:
predeploys
.
L2CrossDomainMessengerAddr
,
XDomainTarget
:
common
.
Address
{
19
:
0x01
},
XDomainSender
:
common
.
Address
{
19
:
0x02
},
XDomainData
:
[]
byte
{
0x01
,
0x02
,
0x03
},
XDomainNonce
:
big
.
NewInt
(
1
),
},
}
// Add an additional witness to the witnesses list to
// test how the prechecker handles witness data that
// isn't in state.
witnessWds
:=
append
([]
*
LegacyWithdrawal
{
{
MessageSender
:
common
.
Address
{
19
:
0xAA
},
XDomainTarget
:
common
.
Address
{
19
:
0x03
},
XDomainSender
:
predeploys
.
L2CrossDomainMessengerAddr
,
XDomainData
:
[]
byte
{
0x01
,
0x02
,
0x03
},
XDomainNonce
:
big
.
NewInt
(
0
),
},
},
dbWds
...
)
filteredWds
,
err
:=
runPrecheck
(
t
,
dbWds
,
witnessWds
)
require
.
NoError
(
t
,
err
)
require
.
EqualValues
(
t
,
[]
*
LegacyWithdrawal
{
dbWds
[
1
]},
filteredWds
)
}
func
TestPreCheckWithdrawals_InvalidSlotInStorage
(
t
*
testing
.
T
)
{
rawDB
:=
rawdb
.
NewMemoryDatabase
()
rawStateDB
:=
state
.
NewDatabaseWithConfig
(
rawDB
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
})
stateDB
,
err
:=
state
.
New
(
common
.
Hash
{},
rawStateDB
,
nil
)
require
.
NoError
(
t
,
err
)
// Create account, and set a random storage slot to a value
// other than abiTrue.
stateDB
.
CreateAccount
(
predeploys
.
LegacyMessagePasserAddr
)
stateDB
.
SetState
(
predeploys
.
LegacyMessagePasserAddr
,
common
.
Hash
{
0
:
0xff
},
common
.
Hash
{
0
:
0xff
})
root
,
err
:=
stateDB
.
Commit
(
false
)
require
.
NoError
(
t
,
err
)
err
=
stateDB
.
Database
()
.
TrieDB
()
.
Commit
(
root
,
true
)
require
.
NoError
(
t
,
err
)
_
,
err
=
PreCheckWithdrawals
(
stateDB
,
nil
,
nil
)
require
.
ErrorIs
(
t
,
err
,
ErrUnknownSlotInMessagePasser
)
}
func
TestPreCheckWithdrawals_MissingStorageSlot
(
t
*
testing
.
T
)
{
// Add a legacy WD to state that does not appear in witness data.
dbWds
:=
[]
*
LegacyWithdrawal
{
{
XDomainTarget
:
common
.
Address
{
19
:
0x01
},
XDomainSender
:
predeploys
.
L2CrossDomainMessengerAddr
,
XDomainData
:
[]
byte
{
0x01
,
0x02
,
0x03
},
XDomainNonce
:
big
.
NewInt
(
1
),
},
}
// Create some witness data that includes both a valid
// and an invalid witness, but neither of which correspond
// to the value above in state.
witnessWds
:=
[]
*
LegacyWithdrawal
{
{
XDomainTarget
:
common
.
Address
{
19
:
0x01
},
XDomainSender
:
common
.
Address
{
19
:
0x02
},
XDomainData
:
[]
byte
{
0x01
,
0x02
,
0x03
},
XDomainNonce
:
big
.
NewInt
(
0
),
},
{
XDomainTarget
:
common
.
Address
{
19
:
0x03
},
XDomainSender
:
predeploys
.
L2CrossDomainMessengerAddr
,
XDomainData
:
[]
byte
{
0x01
,
0x02
,
0x03
},
XDomainNonce
:
big
.
NewInt
(
0
),
},
}
_
,
err
:=
runPrecheck
(
t
,
dbWds
,
witnessWds
)
require
.
ErrorIs
(
t
,
err
,
ErrMissingSlotInWitness
)
}
func
runPrecheck
(
t
*
testing
.
T
,
dbWds
[]
*
LegacyWithdrawal
,
witnessWds
[]
*
LegacyWithdrawal
)
([]
*
LegacyWithdrawal
,
error
)
{
rawDB
:=
rawdb
.
NewMemoryDatabase
()
rawStateDB
:=
state
.
NewDatabaseWithConfig
(
rawDB
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
})
stateDB
,
err
:=
state
.
New
(
common
.
Hash
{},
rawStateDB
,
nil
)
require
.
NoError
(
t
,
err
)
stateDB
.
CreateAccount
(
predeploys
.
LegacyMessagePasserAddr
)
for
_
,
wd
:=
range
dbWds
{
slot
,
err
:=
wd
.
StorageSlot
()
require
.
NoError
(
t
,
err
)
stateDB
.
SetState
(
predeploys
.
LegacyMessagePasserAddr
,
slot
,
abiTrue
)
}
root
,
err
:=
stateDB
.
Commit
(
false
)
require
.
NoError
(
t
,
err
)
err
=
stateDB
.
Database
()
.
TrieDB
()
.
Commit
(
root
,
true
)
require
.
NoError
(
t
,
err
)
return
PreCheckWithdrawals
(
stateDB
,
witnessWds
,
nil
)
}
op-chain-ops/crossdomain/types.go
View file @
dfee56f5
package
crossdomain
package
crossdomain
import
(
import
(
"fmt"
"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/crypto"
)
)
// DangerousUnfilteredWithdrawals is a list of raw withdrawal witness
// data. It has not been filtered for messages from sources other than
// the
type
DangerousUnfilteredWithdrawals
[]
*
LegacyWithdrawal
// SafeFilteredWithdrawals is a list of withdrawals that have been filtered to only include
// withdrawals that were from the L2XDM.
type
SafeFilteredWithdrawals
[]
*
LegacyWithdrawal
var
(
var
(
// Standard ABI types
// Standard ABI types
Uint256Type
,
_
=
abi
.
NewType
(
"uint256"
,
""
,
nil
)
Uint256Type
,
_
=
abi
.
NewType
(
"uint256"
,
""
,
nil
)
...
@@ -33,35 +21,3 @@ type WithdrawalMessage interface {
...
@@ -33,35 +21,3 @@ type WithdrawalMessage interface {
Hash
()
(
common
.
Hash
,
error
)
Hash
()
(
common
.
Hash
,
error
)
StorageSlot
()
(
common
.
Hash
,
error
)
StorageSlot
()
(
common
.
Hash
,
error
)
}
}
// InvalidMessage represents a message to the L1 message passer that
// cannot be decoded as a withdrawal. They are defined as a separate
// type in order to completely disambiguate them from any other
// message.
type
InvalidMessage
SentMessage
func
(
msg
*
InvalidMessage
)
Encode
()
([]
byte
,
error
)
{
out
:=
make
([]
byte
,
len
(
msg
.
Msg
)
+
20
)
copy
(
out
,
msg
.
Msg
)
copy
(
out
[
len
(
msg
.
Msg
)
:
],
msg
.
Who
.
Bytes
())
return
out
,
nil
}
func
(
msg
*
InvalidMessage
)
Hash
()
(
common
.
Hash
,
error
)
{
bytes
,
err
:=
msg
.
Encode
()
if
err
!=
nil
{
return
common
.
Hash
{},
fmt
.
Errorf
(
"cannot hash: %w"
,
err
)
}
return
crypto
.
Keccak256Hash
(
bytes
),
nil
}
func
(
msg
*
InvalidMessage
)
StorageSlot
()
(
common
.
Hash
,
error
)
{
hash
,
err
:=
msg
.
Hash
()
if
err
!=
nil
{
return
common
.
Hash
{},
fmt
.
Errorf
(
"cannot compute storage slot: %w"
,
err
)
}
preimage
:=
make
([]
byte
,
64
)
copy
(
preimage
,
hash
.
Bytes
())
return
crypto
.
Keccak256Hash
(
preimage
),
nil
}
op-chain-ops/crossdomain/types_test.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain
import
(
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func
TestInvalidMessage
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
msg
InvalidMessage
slot
common
.
Hash
}{
{
name
:
"unparseable x-domain message on mainnet"
,
msg
:
InvalidMessage
{
Who
:
common
.
HexToAddress
(
"0x8b1d477410344785ff1df52500032e6d5f532ee4"
),
Msg
:
common
.
FromHex
(
"0x042069"
),
},
slot
:
common
.
HexToHash
(
"0x2a49ae6579c3878f10cf87ecdbebc6c4e2b2159ffe2b1af88af6ca9697fc32cb"
),
},
{
name
:
"valid x-domain message on mainnet for validation"
,
msg
:
InvalidMessage
{
Who
:
common
.
HexToAddress
(
"0x4200000000000000000000000000000000000007"
),
Msg
:
common
.
FromHex
(
""
+
"0xcbd4ece900000000000000000000000099c9fc46f92e8a1c0dec1b1747d01090"
+
"3e884be100000000000000000000000042000000000000000000000000000000"
+
"0000001000000000000000000000000000000000000000000000000000000000"
+
"0000008000000000000000000000000000000000000000000000000000000000"
+
"00019be200000000000000000000000000000000000000000000000000000000"
+
"000000e4a9f9e675000000000000000000000000a0b86991c6218b36c1d19d4a"
+
"2e9eb0ce3606eb480000000000000000000000007f5c764cbc14f9669b88837c"
+
"a1490cca17c31607000000000000000000000000a420b2d1c0841415a695b81e"
+
"5b867bcd07dff8c9000000000000000000000000c186fa914353c44b2e33ebe0"
+
"5f21846f1048beda000000000000000000000000000000000000000000000000"
+
"00000000295d681d000000000000000000000000000000000000000000000000"
+
"00000000000000c0000000000000000000000000000000000000000000000000"
+
"0000000000000000000000000000000000000000000000000000000000000000"
+
"00000000"
,
),
},
slot
:
common
.
HexToHash
(
"0x8f8f6be7a4c5048f46ca41897181d17c10c39365ead5ac27c23d1e8e466d0ed5"
),
},
}
for
_
,
test
:=
range
tests
{
t
.
Run
(
test
.
name
,
func
(
t
*
testing
.
T
)
{
// StorageSlot() tests Hash() and Encode() so we don't
// need to test these separately.
slot
,
err
:=
test
.
msg
.
StorageSlot
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
test
.
slot
,
slot
)
})
}
}
op-chain-ops/crossdomain/withdrawals.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain
import
(
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
// A PendingWithdrawal represents a withdrawal that has
// not been finalized on L1
type
PendingWithdrawal
struct
{
LegacyWithdrawal
`json:"withdrawal"`
TransactionHash
common
.
Hash
`json:"transactionHash"`
}
// Backends represents a set of backends for L1 and L2.
// These are used as the backends for the Messengers
type
Backends
struct
{
L1
bind
.
ContractBackend
L2
bind
.
ContractBackend
}
func
NewBackends
(
l1
,
l2
bind
.
ContractBackend
)
*
Backends
{
return
&
Backends
{
L1
:
l1
,
L2
:
l2
,
}
}
// Messengers represents a pair of L1 and L2 cross domain messengers
// that are connected to the correct contract addresses
type
Messengers
struct
{
L1
*
bindings
.
L1CrossDomainMessenger
L2
*
bindings
.
L2CrossDomainMessenger
}
// NewMessengers constructs Messengers. Passing in the address of the
// L1CrossDomainMessenger is required to connect to the
func
NewMessengers
(
backends
*
Backends
,
l1CrossDomainMessenger
common
.
Address
)
(
*
Messengers
,
error
)
{
l1Messenger
,
err
:=
bindings
.
NewL1CrossDomainMessenger
(
l1CrossDomainMessenger
,
backends
.
L1
)
if
err
!=
nil
{
return
nil
,
err
}
l2Messenger
,
err
:=
bindings
.
NewL2CrossDomainMessenger
(
predeploys
.
L2CrossDomainMessengerAddr
,
backends
.
L2
)
if
err
!=
nil
{
return
nil
,
err
}
return
&
Messengers
{
L1
:
l1Messenger
,
L2
:
l2Messenger
,
},
nil
}
// GetPendingWithdrawals will fetch pending withdrawals by getting
// L2CrossDomainMessenger `SentMessage` events and then checking to see if the
// cross domain message hash has been finalized on L1. It will return a slice of
// PendingWithdrawals that have not been finalized on L1.
func
GetPendingWithdrawals
(
messengers
*
Messengers
,
version
*
big
.
Int
,
start
,
end
uint64
)
([]
PendingWithdrawal
,
error
)
{
withdrawals
:=
make
([]
PendingWithdrawal
,
0
)
// This will not take into account "pending" state, this ensures that
// transactions in the mempool are upgraded as well.
opts
:=
bind
.
FilterOpts
{
Start
:
start
,
}
// Only set the end block range if end is non zero. When end is zero, the
// filter will extend to the latest block.
if
end
!=
0
{
opts
.
End
=
&
end
}
messages
,
err
:=
messengers
.
L2
.
FilterSentMessage
(
&
opts
,
nil
)
if
err
!=
nil
{
return
nil
,
err
}
defer
messages
.
Close
()
for
messages
.
Next
()
{
event
:=
messages
.
Event
msg
:=
NewCrossDomainMessage
(
event
.
MessageNonce
,
event
.
Sender
,
event
.
Target
,
common
.
Big0
,
event
.
GasLimit
,
event
.
Message
,
)
// Optional version check
if
version
!=
nil
{
if
version
.
Uint64
()
!=
msg
.
Version
()
{
return
nil
,
fmt
.
Errorf
(
"expected version %d, got version %d"
,
version
,
msg
.
Version
())
}
}
hash
,
err
:=
msg
.
Hash
()
if
err
!=
nil
{
return
nil
,
err
}
relayed
,
err
:=
messengers
.
L1
.
SuccessfulMessages
(
&
bind
.
CallOpts
{},
hash
)
if
err
!=
nil
{
return
nil
,
err
}
if
!
relayed
{
log
.
Info
(
"%s not yet relayed"
,
event
.
Raw
.
TxHash
)
withdrawal
:=
PendingWithdrawal
{
LegacyWithdrawal
:
LegacyWithdrawal
{
XDomainTarget
:
event
.
Target
,
XDomainSender
:
event
.
Sender
,
XDomainData
:
event
.
Message
,
XDomainNonce
:
event
.
MessageNonce
,
},
TransactionHash
:
event
.
Raw
.
TxHash
,
}
withdrawals
=
append
(
withdrawals
,
withdrawal
)
}
else
{
log
.
Info
(
"%s already relayed"
,
event
.
Raw
.
TxHash
)
}
}
return
withdrawals
,
nil
}
op-chain-ops/crossdomain/withdrawals_test.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain_test
import
(
"context"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/stretchr/testify/require"
"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/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
)
var
(
// testKey is the same test key that geth uses
testKey
,
_
=
crypto
.
HexToECDSA
(
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
)
// chainID is the chain id used for simulated backends
chainID
=
big
.
NewInt
(
1337
)
// testAccount represents the sender account for tests
testAccount
=
crypto
.
PubkeyToAddress
(
testKey
.
PublicKey
)
)
// sendMessageArgs represents the input to `SendMessage`. The value
// is excluded specifically here because we want to simulate v0 messages
// as closely as possible.
type
sendMessageArgs
struct
{
Target
common
.
Address
Message
[]
byte
MinGasLimit
uint32
}
// setL1CrossDomainMessenger will set the L1CrossDomainMessenger into
// a state db that represents L1. It accepts a list of "successfulMessages"
// to be placed into the state. This allows for a subset of messages that
// were withdrawn on L2 to be placed into the L1 state to simulate
// a set of withdrawals that are not finalized on L1
func
setL1CrossDomainMessenger
(
db
vm
.
StateDB
,
successful
[]
common
.
Hash
)
error
{
bytecode
,
err
:=
bindings
.
GetDeployedBytecode
(
"L1CrossDomainMessenger"
)
if
err
!=
nil
{
return
err
}
db
.
CreateAccount
(
predeploys
.
DevL1CrossDomainMessengerAddr
)
db
.
SetCode
(
predeploys
.
DevL1CrossDomainMessengerAddr
,
bytecode
)
msgs
:=
make
(
map
[
any
]
any
)
for
_
,
hash
:=
range
successful
{
msgs
[
hash
]
=
true
}
return
state
.
SetStorage
(
"L1CrossDomainMessenger"
,
predeploys
.
DevL1CrossDomainMessengerAddr
,
state
.
StorageValues
{
"successfulMessages"
:
msgs
,
},
db
,
)
}
// setL2CrossDomainMessenger will set the L2CrossDomainMessenger into
// a state db that represents L2. It does not set any state as the only
// function called in this test is "sendMessage" which calls a hardcoded
// address that represents the L2ToL1MessagePasser
func
setL2CrossDomainMessenger
(
db
vm
.
StateDB
)
error
{
bytecode
,
err
:=
bindings
.
GetDeployedBytecode
(
"L2CrossDomainMessenger"
)
if
err
!=
nil
{
return
err
}
db
.
CreateAccount
(
predeploys
.
L2CrossDomainMessengerAddr
)
db
.
SetCode
(
predeploys
.
L2CrossDomainMessengerAddr
,
bytecode
)
return
state
.
SetStorage
(
"L2CrossDomainMessenger"
,
predeploys
.
L2CrossDomainMessengerAddr
,
state
.
StorageValues
{
"successfulMessages"
:
map
[
any
]
any
{},
},
db
,
)
}
// setL2ToL1MessagePasser will set the L2ToL1MessagePasser into a state
// db that represents L2. This must be set so the L2CrossDomainMessenger
// can call it as part of "sendMessage"
func
setL2ToL1MessagePasser
(
db
vm
.
StateDB
)
error
{
bytecode
,
err
:=
bindings
.
GetDeployedBytecode
(
"L2ToL1MessagePasser"
)
if
err
!=
nil
{
return
err
}
db
.
CreateAccount
(
predeploys
.
L2ToL1MessagePasserAddr
)
db
.
SetCode
(
predeploys
.
L2ToL1MessagePasserAddr
,
bytecode
)
return
state
.
SetStorage
(
"L2ToL1MessagePasser"
,
predeploys
.
L2ToL1MessagePasserAddr
,
state
.
StorageValues
{},
db
,
)
}
// sendCrossDomainMessage will send a L2 to L1 cross domain message.
// The state cannot just be set because logs must be generated by
// transaction execution
func
sendCrossDomainMessage
(
l2xdm
*
bindings
.
L2CrossDomainMessenger
,
backend
*
backends
.
SimulatedBackend
,
message
*
sendMessageArgs
,
t
*
testing
.
T
,
)
*
crossdomain
.
CrossDomainMessage
{
opts
,
err
:=
bind
.
NewKeyedTransactorWithChainID
(
testKey
,
chainID
)
require
.
Nil
(
t
,
err
)
tx
,
err
:=
l2xdm
.
SendMessage
(
opts
,
message
.
Target
,
message
.
Message
,
message
.
MinGasLimit
)
require
.
Nil
(
t
,
err
)
backend
.
Commit
()
receipt
,
err
:=
backend
.
TransactionReceipt
(
context
.
Background
(),
tx
.
Hash
())
require
.
Nil
(
t
,
err
)
abi
,
_
:=
bindings
.
L2CrossDomainMessengerMetaData
.
GetAbi
()
var
msg
crossdomain
.
CrossDomainMessage
// Ensure that we see the event so that a default CrossDomainMessage
// is not returned
seen
:=
false
// Assume there is only 1 deposit per transaction
for
_
,
log
:=
range
receipt
.
Logs
{
event
,
_
:=
abi
.
EventByID
(
log
.
Topics
[
0
])
// Not the event we are looking for
if
event
==
nil
{
continue
}
// Parse the legacy event
if
event
.
Name
==
"SentMessage"
{
e
,
_
:=
l2xdm
.
ParseSentMessage
(
*
log
)
msg
.
Target
=
e
.
Target
msg
.
Sender
=
e
.
Sender
msg
.
Data
=
e
.
Message
msg
.
Nonce
=
e
.
MessageNonce
msg
.
GasLimit
=
e
.
GasLimit
// Set seen to true to ensure that this event
// was observed
seen
=
true
}
// Parse the new extension event
if
event
.
Name
==
"SentMessageExtension1"
{
e
,
_
:=
l2xdm
.
ParseSentMessageExtension1
(
*
log
)
msg
.
Value
=
e
.
Value
}
}
require
.
True
(
t
,
seen
)
return
&
msg
}
// TestGetPendingWithdrawals tests the high level function used
// to fetch pending withdrawals
func
TestGetPendingWithdrawals
(
t
*
testing
.
T
)
{
// Create a L2 db
L2db
:=
state
.
NewMemoryStateDB
(
nil
)
// Set the test account and give it a large balance
L2db
.
CreateAccount
(
testAccount
)
L2db
.
AddBalance
(
testAccount
,
big
.
NewInt
(
10000000000000000
))
// Set the L2ToL1MessagePasser in the L2 state
err
:=
setL2ToL1MessagePasser
(
L2db
)
require
.
Nil
(
t
,
err
)
// Set the L2CrossDomainMessenger in the L2 state
err
=
setL2CrossDomainMessenger
(
L2db
)
require
.
Nil
(
t
,
err
)
L2
:=
backends
.
NewSimulatedBackend
(
L2db
.
Genesis
()
.
Alloc
,
15000000
,
)
L2CrossDomainMessenger
,
err
:=
bindings
.
NewL2CrossDomainMessenger
(
predeploys
.
L2CrossDomainMessengerAddr
,
L2
,
)
require
.
Nil
(
t
,
err
)
// Create a set of test data that is made up of cross domain messages.
// There is a total of 6 cross domain messages. 3 of them are set to be
// finalized on L1 so 3 of them will be considered not finalized.
msgs
:=
[]
*
sendMessageArgs
{
{
Target
:
common
.
Address
{},
Message
:
[]
byte
{},
MinGasLimit
:
0
,
},
{
Target
:
common
.
Address
{
0x01
},
Message
:
[]
byte
{
0x01
},
MinGasLimit
:
0
,
},
{
Target
:
common
.
Address
{},
Message
:
[]
byte
{},
MinGasLimit
:
100
,
},
{
Target
:
common
.
Address
{
19
:
0x01
},
Message
:
[]
byte
{
0xaa
,
0xbb
},
MinGasLimit
:
10000
,
},
{
Target
:
common
.
HexToAddress
(
"0x4675C7e5BaAFBFFbca748158bEcBA61ef3b0a263"
),
Message
:
hexutil
.
MustDecode
(
"0x095ea7b3000000000000000000000000c92e8bdf79f0507f65a392b0ab4667716bfe01100000000000000000000000000000000000000000000000000000000000000000"
),
MinGasLimit
:
50000
,
},
{
Target
:
common
.
HexToAddress
(
"0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5"
),
Message
:
[]
byte
{},
MinGasLimit
:
70511
,
},
}
// For each test cross domain message, call "sendMessage" on the
// L2CrossDomainMessenger and compute the cross domain message hash
hashes
:=
make
([]
common
.
Hash
,
len
(
msgs
))
for
i
,
msg
:=
range
msgs
{
sent
:=
sendCrossDomainMessage
(
L2CrossDomainMessenger
,
L2
,
msg
,
t
)
hash
,
err
:=
sent
.
Hash
()
require
.
Nil
(
t
,
err
)
hashes
[
i
]
=
hash
}
// Create a L1 backend with a dev account
L1db
:=
state
.
NewMemoryStateDB
(
nil
)
L1db
.
CreateAccount
(
testAccount
)
L1db
.
AddBalance
(
testAccount
,
big
.
NewInt
(
10000000000000000
))
// Set the L1CrossDomainMessenger into the L1 state. Only set a subset
// of the messages as finalized, the first 3.
err
=
setL1CrossDomainMessenger
(
L1db
,
hashes
[
0
:
3
])
require
.
Nil
(
t
,
err
)
L1
:=
backends
.
NewSimulatedBackend
(
L1db
.
Genesis
()
.
Alloc
,
15000000
,
)
backends
:=
crossdomain
.
NewBackends
(
L1
,
L2
)
messengers
,
err
:=
crossdomain
.
NewMessengers
(
backends
,
predeploys
.
DevL1CrossDomainMessengerAddr
)
require
.
Nil
(
t
,
err
)
// Fetch the pending withdrawals
withdrawals
,
err
:=
crossdomain
.
GetPendingWithdrawals
(
messengers
,
nil
,
0
,
100
)
require
.
Nil
(
t
,
err
)
// Since only half of the withdrawals were set as finalized on L1,
// the number of pending withdrawals should be 3
require
.
Equal
(
t
,
3
,
len
(
withdrawals
))
// The final 3 test cross domain messages should be equal to the
// fetched pending withdrawals. This shows that `GetPendingWithdrawals`
// fetched the correct messages
for
i
,
msg
:=
range
msgs
[
3
:
]
{
withdrawal
:=
withdrawals
[
i
]
require
.
Equal
(
t
,
msg
.
Target
,
withdrawal
.
XDomainTarget
)
require
.
Equal
(
t
,
msg
.
Message
,
[]
byte
(
withdrawal
.
XDomainData
))
}
}
op-chain-ops/crossdomain/witness.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain
import
(
"bufio"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// SentMessage represents an entry in the JSON file that is created by
// the `migration-data` package. Each entry represents a call to the
// `LegacyMessagePasser`. The `who` should always be the
// `L2CrossDomainMessenger` and the `msg` should be an abi encoded
// `relayMessage(address,address,bytes,uint256)`
type
SentMessage
struct
{
Who
common
.
Address
`json:"who"`
Msg
hexutil
.
Bytes
`json:"msg"`
}
// NewSentMessageFromJSON will read a JSON file from disk given a path to the JSON
// file. The JSON file this function reads from disk is an output from the
// `migration-data` package.
func
NewSentMessageFromJSON
(
path
string
)
([]
*
SentMessage
,
error
)
{
file
,
err
:=
os
.
ReadFile
(
path
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot find sent message json at %s: %w"
,
path
,
err
)
}
var
j
[]
*
SentMessage
if
err
:=
json
.
Unmarshal
(
file
,
&
j
);
err
!=
nil
{
return
nil
,
err
}
return
j
,
nil
}
// decodeWitnessCalldata abi decodes the calldata encoded in the input witness
// file. It errors if the 4 byte selector is not specifically for `passMessageToL1`.
// It also errors if the abi decoding fails.
func
decodeWitnessCalldata
(
msg
[]
byte
)
([]
byte
,
error
)
{
abi
,
err
:=
bindings
.
LegacyMessagePasserMetaData
.
GetAbi
()
if
err
!=
nil
{
panic
(
"should always be able to get message passer abi"
)
}
if
size
:=
len
(
msg
);
size
<
4
{
return
nil
,
fmt
.
Errorf
(
"message too short: %d"
,
size
)
}
method
,
err
:=
abi
.
MethodById
(
msg
[
:
4
])
if
err
!=
nil
{
return
nil
,
err
}
if
method
.
Sig
!=
"passMessageToL1(bytes)"
{
return
nil
,
fmt
.
Errorf
(
"unknown method: %s"
,
method
.
Name
)
}
out
,
err
:=
method
.
Inputs
.
Unpack
(
msg
[
4
:
])
if
err
!=
nil
{
return
nil
,
err
}
cast
,
ok
:=
out
[
0
]
.
([]
byte
)
if
!
ok
{
panic
(
"should always be able to cast type []byte"
)
}
return
cast
,
nil
}
// ReadWitnessData will read messages and addresses from a raw l2geth state
// dump file.
func
ReadWitnessData
(
path
string
)
([]
*
SentMessage
,
OVMETHAddresses
,
error
)
{
f
,
err
:=
os
.
Open
(
path
)
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"cannot open witness data file: %w"
,
err
)
}
defer
f
.
Close
()
scan
:=
bufio
.
NewScanner
(
f
)
var
witnesses
[]
*
SentMessage
addresses
:=
make
(
map
[
common
.
Address
]
bool
)
for
scan
.
Scan
()
{
line
:=
scan
.
Text
()
splits
:=
strings
.
Split
(
line
,
"|"
)
if
len
(
splits
)
<
2
{
return
nil
,
nil
,
fmt
.
Errorf
(
"invalid line: %s"
,
line
)
}
switch
splits
[
0
]
{
case
"MSG"
:
if
len
(
splits
)
!=
3
{
return
nil
,
nil
,
fmt
.
Errorf
(
"invalid line: %s"
,
line
)
}
msg
:=
splits
[
2
]
// Make sure that the witness data has a 0x prefix
if
!
strings
.
HasPrefix
(
msg
,
"0x"
)
{
msg
=
"0x"
+
msg
}
msgB
:=
hexutil
.
MustDecode
(
msg
)
// Skip any errors
calldata
,
err
:=
decodeWitnessCalldata
(
msgB
)
if
err
!=
nil
{
log
.
Warn
(
"cannot decode witness calldata"
,
"err"
,
err
)
continue
}
witnesses
=
append
(
witnesses
,
&
SentMessage
{
Who
:
common
.
HexToAddress
(
splits
[
1
]),
Msg
:
calldata
,
})
case
"ETH"
:
addresses
[
common
.
HexToAddress
(
splits
[
1
])]
=
true
default
:
return
nil
,
nil
,
fmt
.
Errorf
(
"invalid line: %s"
,
line
)
}
}
return
witnesses
,
addresses
,
nil
}
// ToLegacyWithdrawal will convert a SentMessageJSON to a LegacyWithdrawal
// struct. This is useful because the LegacyWithdrawal struct has helper
// functions on it that can compute the withdrawal hash and the storage slot.
func
(
s
*
SentMessage
)
ToLegacyWithdrawal
()
(
*
LegacyWithdrawal
,
error
)
{
data
:=
make
([]
byte
,
len
(
s
.
Who
)
+
len
(
s
.
Msg
))
copy
(
data
,
s
.
Msg
)
copy
(
data
[
len
(
s
.
Msg
)
:
],
s
.
Who
[
:
])
var
w
LegacyWithdrawal
if
err
:=
w
.
Decode
(
data
);
err
!=
nil
{
return
nil
,
err
}
return
&
w
,
nil
}
// OVMETHAddresses represents a list of addresses that interacted with
// the ERC20 representation of ether in the pre-bedrock system.
type
OVMETHAddresses
map
[
common
.
Address
]
bool
// NewAddresses will read an addresses.json file from the filesystem.
func
NewAddresses
(
path
string
)
(
OVMETHAddresses
,
error
)
{
file
,
err
:=
os
.
ReadFile
(
path
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot find addresses json at %s: %w"
,
path
,
err
)
}
var
addresses
[]
common
.
Address
if
err
:=
json
.
Unmarshal
(
file
,
&
addresses
);
err
!=
nil
{
return
nil
,
err
}
ovmeth
:=
make
(
OVMETHAddresses
)
for
_
,
addr
:=
range
addresses
{
ovmeth
[
addr
]
=
true
}
return
ovmeth
,
nil
}
// Allowance represents the allowances that were set in the
// legacy ERC20 representation of ether
type
Allowance
struct
{
From
common
.
Address
`json:"fr"`
To
common
.
Address
`json:"to"`
}
// NewAllowances will read the ovm-allowances.json from the file system.
func
NewAllowances
(
path
string
)
([]
*
Allowance
,
error
)
{
file
,
err
:=
os
.
ReadFile
(
path
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot find allowances json at %s: %w"
,
path
,
err
)
}
var
allowances
[]
*
Allowance
if
err
:=
json
.
Unmarshal
(
file
,
&
allowances
);
err
!=
nil
{
return
nil
,
err
}
return
allowances
,
nil
}
// MigrationData represents all of the data required to do a migration
type
MigrationData
struct
{
// OvmAddresses represents the set of addresses that interacted with the
// LegacyERC20ETH contract before the evm equivalence upgrade
OvmAddresses
OVMETHAddresses
// EvmAddresses represents the set of addresses that interacted with the
// LegacyERC20ETH contract after the evm equivalence upgrade
EvmAddresses
OVMETHAddresses
// OvmAllowances represents the set of allowances in the LegacyERC20ETH from
// before the evm equivalence upgrade
OvmAllowances
[]
*
Allowance
// OvmMessages represents the set of withdrawals through the
// L2CrossDomainMessenger from before the evm equivalence upgrade
OvmMessages
[]
*
SentMessage
// OvmMessages represents the set of withdrawals through the
// L2CrossDomainMessenger from after the evm equivalence upgrade
EvmMessages
[]
*
SentMessage
}
func
(
m
*
MigrationData
)
ToWithdrawals
()
(
DangerousUnfilteredWithdrawals
,
[]
InvalidMessage
,
error
)
{
messages
:=
make
(
DangerousUnfilteredWithdrawals
,
0
)
invalidMessages
:=
make
([]
InvalidMessage
,
0
)
for
_
,
msg
:=
range
m
.
OvmMessages
{
wd
,
err
:=
msg
.
ToLegacyWithdrawal
()
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"error serializing OVM message: %w"
,
err
)
}
messages
=
append
(
messages
,
wd
)
}
for
_
,
msg
:=
range
m
.
EvmMessages
{
wd
,
err
:=
msg
.
ToLegacyWithdrawal
()
if
err
!=
nil
{
log
.
Warn
(
"Discovered mal-formed withdrawal"
,
"who"
,
msg
.
Who
,
"data"
,
msg
.
Msg
)
invalidMessages
=
append
(
invalidMessages
,
InvalidMessage
(
*
msg
))
continue
}
messages
=
append
(
messages
,
wd
)
}
return
messages
,
invalidMessages
,
nil
}
func
(
m
*
MigrationData
)
Addresses
()
[]
common
.
Address
{
addresses
:=
make
([]
common
.
Address
,
0
)
for
addr
:=
range
m
.
EvmAddresses
{
addresses
=
append
(
addresses
,
addr
)
}
for
addr
:=
range
m
.
OvmAddresses
{
addresses
=
append
(
addresses
,
addr
)
}
return
addresses
}
op-chain-ops/crossdomain/witness_test.go
deleted
100644 → 0
View file @
cedeb4ec
package
crossdomain
import
(
"context"
"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/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func
TestRead
(
t
*
testing
.
T
)
{
witnesses
,
addresses
,
err
:=
ReadWitnessData
(
"testdata/witness.txt"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
[]
*
SentMessage
{
{
Who
:
common
.
HexToAddress
(
"0x4200000000000000000000000000000000000007"
),
Msg
:
common
.
FromHex
(
"0xcbd4ece900000000000000000000000099c9fc46f92e8a1c0dec1b1747d01090"
+
"3e884be100000000000000000000000042000000000000000000000000000000"
+
"0000001000000000000000000000000000000000000000000000000000000000"
+
"0000008000000000000000000000000000000000000000000000000000000000"
+
"00019bd000000000000000000000000000000000000000000000000000000000"
+
"000000e4a9f9e675000000000000000000000000d533a949740bb3306d119cc7"
+
"77fa900ba034cd520000000000000000000000000994206dfe8de6ec6920ff4d"
+
"779b0d950605fb53000000000000000000000000e3a44dd2a8c108be56a78635"
+
"121ec914074da16d000000000000000000000000e3a44dd2a8c108be56a78635"
+
"121ec914074da16d0000000000000000000000000000000000000000000001b0"
+
"ac98ab3858d75478000000000000000000000000000000000000000000000000"
+
"00000000000000c0000000000000000000000000000000000000000000000000"
+
"0000000000000000000000000000000000000000000000000000000000000000"
+
"00000000"
,
),
},
{
Who
:
common
.
HexToAddress
(
"0x8b1d477410344785ff1df52500032e6d5f532ee4"
),
Msg
:
common
.
FromHex
(
"0x042069"
),
},
},
witnesses
)
require
.
Equal
(
t
,
OVMETHAddresses
{
common
.
HexToAddress
(
"0x6340d44c5174588B312F545eEC4a42f8a514eF50"
)
:
true
,
},
addresses
)
}
// TestDecodeWitnessCallData tests that the witness data is parsed correctly
// from an input bytes slice.
func
TestDecodeWitnessCallData
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
err
bool
msg
[]
byte
want
[]
byte
}{
{
name
:
"too-small"
,
err
:
true
,
msg
:
common
.
FromHex
(
"0x0000"
),
},
{
name
:
"unknown-selector"
,
err
:
true
,
msg
:
common
.
FromHex
(
"0x00000000"
),
},
{
name
:
"wrong-selector"
,
err
:
true
,
// 0x54fd4d50 is the selector for `version()`
msg
:
common
.
FromHex
(
"0x54fd4d50"
),
},
{
name
:
"invalid-calldata-only-selector"
,
err
:
true
,
// 0xcafa81dc is the selector for `passMessageToL1(bytes)`
msg
:
common
.
FromHex
(
"0xcafa81dc"
),
},
{
name
:
"invalid-calldata-invalid-bytes"
,
err
:
true
,
// 0xcafa81dc is the selector for passMessageToL1(bytes)
msg
:
common
.
FromHex
(
"0xcafa81dc0000"
),
},
{
name
:
"valid-calldata"
,
msg
:
common
.
FromHex
(
"0xcafa81dc"
+
"0000000000000000000000000000000000000000000000000000000000000020"
+
"0000000000000000000000000000000000000000000000000000000000000002"
+
"1234000000000000000000000000000000000000000000000000000000000000"
,
),
want
:
common
.
FromHex
(
"0x1234"
),
},
}
for
_
,
tt
:=
range
tests
{
test
:=
tt
t
.
Run
(
test
.
name
,
func
(
t
*
testing
.
T
)
{
if
test
.
err
{
_
,
err
:=
decodeWitnessCalldata
(
test
.
msg
)
require
.
Error
(
t
,
err
)
}
else
{
want
,
err
:=
decodeWitnessCalldata
(
test
.
msg
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
test
.
want
,
want
)
}
})
}
}
// TestMessagePasserSafety ensures that the LegacyMessagePasser contract reverts when it is called
// with incorrect calldata. The function signature is correct but the calldata is not abi encoded
// correctly. It is expected the solidity reverts when it cannot abi decode the calldata correctly.
// Only a call to `passMessageToL1` with abi encoded `bytes` will result in the `successfulMessages`
// mapping being updated.
func
TestMessagePasserSafety
(
t
*
testing
.
T
)
{
testKey
,
_
:=
crypto
.
HexToECDSA
(
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
)
testAddr
:=
crypto
.
PubkeyToAddress
(
testKey
.
PublicKey
)
opts
,
err
:=
bind
.
NewKeyedTransactorWithChainID
(
testKey
,
big
.
NewInt
(
1337
))
require
.
NoError
(
t
,
err
)
backend
:=
backends
.
NewSimulatedBackend
(
core
.
GenesisAlloc
{
testAddr
:
{
Balance
:
big
.
NewInt
(
10000000000000000
)}},
30
_000_000
,
)
defer
backend
.
Close
()
// deploy the LegacyMessagePasser contract
addr
,
tx
,
contract
,
err
:=
bindings
.
DeployLegacyMessagePasser
(
opts
,
backend
)
require
.
NoError
(
t
,
err
)
backend
.
Commit
()
_
,
err
=
bind
.
WaitMined
(
context
.
Background
(),
backend
,
tx
)
require
.
NoError
(
t
,
err
)
// ensure that it deployed
code
,
err
:=
backend
.
CodeAt
(
context
.
Background
(),
addr
,
nil
)
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
len
(
code
)
>
0
)
// dummy message
msg
:=
[]
byte
{
0x00
,
0x01
,
0x02
,
0x03
}
// call `passMessageToL1`
msgTx
,
err
:=
contract
.
PassMessageToL1
(
opts
,
msg
)
require
.
NoError
(
t
,
err
)
// ensure that the receipt is successful
backend
.
Commit
()
msgReceipt
,
err
:=
bind
.
WaitMined
(
context
.
Background
(),
backend
,
msgTx
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
msgReceipt
.
Status
,
types
.
ReceiptStatusSuccessful
)
// check for the data in the `successfulMessages` mapping
data
:=
make
([]
byte
,
len
(
msg
)
+
len
(
testAddr
))
copy
(
data
[
:
],
msg
)
copy
(
data
[
len
(
msg
)
:
],
testAddr
.
Bytes
())
digest
:=
crypto
.
Keccak256Hash
(
data
)
contains
,
err
:=
contract
.
SentMessages
(
&
bind
.
CallOpts
{},
digest
)
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
contains
)
// build a transaction with improperly formatted calldata
nonce
,
err
:=
backend
.
NonceAt
(
context
.
Background
(),
testAddr
,
nil
)
require
.
NoError
(
t
,
err
)
// append msg without abi encoding it
selector
:=
crypto
.
Keccak256
([]
byte
(
"passMessageToL1(bytes)"
))[
0
:
4
]
require
.
Equal
(
t
,
selector
,
hexutil
.
MustDecode
(
"0xcafa81dc"
))
calldata
:=
append
(
selector
,
msg
...
)
faultyTransaction
,
err
:=
opts
.
Signer
(
testAddr
,
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
ChainID
:
big
.
NewInt
(
1337
),
Nonce
:
nonce
,
GasTipCap
:
msgTx
.
GasTipCap
(),
GasFeeCap
:
msgTx
.
GasFeeCap
(),
Gas
:
msgTx
.
Gas
()
*
2
,
To
:
msgTx
.
To
(),
Data
:
calldata
,
}))
require
.
NoError
(
t
,
err
)
err
=
backend
.
SendTransaction
(
context
.
Background
(),
faultyTransaction
)
require
.
NoError
(
t
,
err
)
// the transaction should revert
backend
.
Commit
()
badReceipt
,
err
:=
bind
.
WaitMined
(
context
.
Background
(),
backend
,
faultyTransaction
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
badReceipt
.
Status
,
types
.
ReceiptStatusFailed
)
// test the transaction calldata against the abi unpacking
abi
,
err
:=
bindings
.
LegacyMessagePasserMetaData
.
GetAbi
()
require
.
NoError
(
t
,
err
)
method
,
err
:=
abi
.
MethodById
(
selector
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
method
.
Name
,
"passMessageToL1"
)
// the faulty transaction has the correct 4 byte selector but doesn't
// have abi encoded bytes following it
require
.
Equal
(
t
,
faultyTransaction
.
Data
()[
:
4
],
selector
)
_
,
err
=
method
.
Inputs
.
Unpack
(
faultyTransaction
.
Data
()[
4
:
])
require
.
Error
(
t
,
err
)
// the original transaction has the correct 4 byte selector and abi encoded bytes
_
,
err
=
method
.
Inputs
.
Unpack
(
msgTx
.
Data
()[
4
:
])
require
.
NoError
(
t
,
err
)
}
op-chain-ops/ether/addresses.go
deleted
100644 → 0
View file @
cedeb4ec
package
ether
import
(
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
)
var
(
// AddressPreimagePrefix is the byte prefix of address preimages
// in Geth's database.
AddressPreimagePrefix
=
[]
byte
(
"addr-preimage-"
)
// ErrStopIteration will stop iterators early when returned from the
// iterator's callback.
ErrStopIteration
=
errors
.
New
(
"iteration stopped"
)
// MintTopic is the topic for mint events on OVM ETH.
MintTopic
=
common
.
HexToHash
(
"0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885"
)
)
type
AddressCB
func
(
address
common
.
Address
)
error
type
AddressCBWithHead
func
(
address
common
.
Address
,
headNum
uint64
)
error
type
AllowanceCB
func
(
owner
,
spender
common
.
Address
)
error
// IterateDBAddresses iterates over each address in Geth's address
// preimage database, calling the callback with the address.
func
IterateDBAddresses
(
db
ethdb
.
Database
,
cb
AddressCB
)
error
{
iter
:=
db
.
NewIterator
(
AddressPreimagePrefix
,
nil
)
for
iter
.
Next
()
{
if
iter
.
Error
()
!=
nil
{
return
iter
.
Error
()
}
addr
:=
common
.
BytesToAddress
(
bytes
.
TrimPrefix
(
iter
.
Key
(),
AddressPreimagePrefix
))
cbErr
:=
cb
(
addr
)
if
cbErr
==
ErrStopIteration
{
return
nil
}
if
cbErr
!=
nil
{
return
cbErr
}
}
return
iter
.
Error
()
}
// IterateAddrList iterates over each address in an address list,
// calling the callback with the address.
func
IterateAddrList
(
r
io
.
Reader
,
cb
AddressCB
)
error
{
scan
:=
bufio
.
NewScanner
(
r
)
for
scan
.
Scan
()
{
addrStr
:=
scan
.
Text
()
if
!
common
.
IsHexAddress
(
addrStr
)
{
return
fmt
.
Errorf
(
"invalid address %s"
,
addrStr
)
}
err
:=
cb
(
common
.
HexToAddress
(
addrStr
))
if
err
==
ErrStopIteration
{
return
nil
}
if
err
!=
nil
{
return
err
}
}
return
nil
}
// IterateAllowanceList iterates over each address in an allowance list,
// calling the callback with the owner and the spender.
func
IterateAllowanceList
(
r
io
.
Reader
,
cb
AllowanceCB
)
error
{
scan
:=
bufio
.
NewScanner
(
r
)
for
scan
.
Scan
()
{
line
:=
scan
.
Text
()
splits
:=
strings
.
Split
(
line
,
","
)
if
len
(
splits
)
!=
2
{
return
fmt
.
Errorf
(
"invalid allowance %s"
,
line
)
}
owner
:=
splits
[
0
]
spender
:=
splits
[
1
]
if
!
common
.
IsHexAddress
(
owner
)
{
return
fmt
.
Errorf
(
"invalid address %s"
,
owner
)
}
if
!
common
.
IsHexAddress
(
spender
)
{
return
fmt
.
Errorf
(
"invalid address %s"
,
spender
)
}
err
:=
cb
(
common
.
HexToAddress
(
owner
),
common
.
HexToAddress
(
spender
))
if
err
==
ErrStopIteration
{
return
nil
}
}
return
nil
}
// IterateMintEvents iterates over each mint event in the database starting
// from head and stopping at genesis.
func
IterateMintEvents
(
db
ethdb
.
Database
,
headNum
uint64
,
cb
AddressCBWithHead
,
progressCb
func
(
uint64
))
error
{
for
headNum
>
0
{
hash
:=
rawdb
.
ReadCanonicalHash
(
db
,
headNum
)
receipts
,
err
:=
crossdomain
.
ReadLegacyReceipts
(
db
,
hash
,
headNum
)
if
err
!=
nil
{
return
err
}
for
_
,
receipt
:=
range
receipts
{
for
_
,
l
:=
range
receipt
.
Logs
{
if
l
.
Address
!=
predeploys
.
LegacyERC20ETHAddr
{
continue
}
if
common
.
BytesToHash
(
l
.
Topics
[
0
]
.
Bytes
())
!=
MintTopic
{
continue
}
err
:=
cb
(
common
.
BytesToAddress
(
l
.
Topics
[
1
][
12
:
]),
headNum
)
if
errors
.
Is
(
err
,
ErrStopIteration
)
{
return
nil
}
if
err
!=
nil
{
return
err
}
}
}
progressCb
(
headNum
)
headNum
--
}
return
nil
}
op-chain-ops/ether/cli.go
deleted
100644 → 0
View file @
cedeb4ec
package
ether
import
(
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
)
// getOVMETHTotalSupply returns OVM ETH's total supply by reading
// the appropriate storage slot.
func
getOVMETHTotalSupply
(
db
*
state
.
StateDB
)
*
big
.
Int
{
key
:=
getOVMETHTotalSupplySlot
()
return
db
.
GetState
(
OVMETHAddress
,
key
)
.
Big
()
}
func
getOVMETHTotalSupplySlot
()
common
.
Hash
{
position
:=
common
.
Big2
key
:=
common
.
BytesToHash
(
common
.
LeftPadBytes
(
position
.
Bytes
(),
32
))
return
key
}
func
GetOVMETHTotalSupplySlot
()
common
.
Hash
{
return
getOVMETHTotalSupplySlot
()
}
// GetOVMETHBalance gets a user's OVM ETH balance from state by querying the
// appropriate storage slot directly.
func
GetOVMETHBalance
(
db
*
state
.
StateDB
,
addr
common
.
Address
)
*
big
.
Int
{
return
db
.
GetState
(
OVMETHAddress
,
CalcOVMETHStorageKey
(
addr
))
.
Big
()
}
op-chain-ops/ether/db.go
deleted
100644 → 0
View file @
cedeb4ec
package
ether
import
(
"path/filepath"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)
// MustOpenDB opens a Geth database, or panics. Note that
// the database must be opened with a freezer in order to
// properly read historical data.
func
MustOpenDB
(
dataDir
string
)
ethdb
.
Database
{
return
MustOpenDBWithCacheOpts
(
dataDir
,
0
,
0
)
}
// MustOpenDBWithCacheOpts opens a Geth database or panics. Allows
// the caller to pass in LevelDB cache parameters.
func
MustOpenDBWithCacheOpts
(
dataDir
string
,
cacheSize
,
handles
int
)
ethdb
.
Database
{
dir
:=
filepath
.
Join
(
dataDir
,
"geth"
,
"chaindata"
)
db
,
err
:=
rawdb
.
Open
(
rawdb
.
OpenOptions
{
Type
:
"leveldb"
,
Directory
:
dir
,
AncientsDirectory
:
filepath
.
Join
(
dir
,
"ancient"
),
Namespace
:
""
,
Cache
:
cacheSize
,
Handles
:
handles
,
ReadOnly
:
true
,
})
if
err
!=
nil
{
log
.
Crit
(
"error opening raw DB"
,
"err"
,
err
)
}
return
db
}
op-chain-ops/ether/migrate.go
deleted
100644 → 0
View file @
cedeb4ec
package
ether
import
(
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log"
)
const
(
// checkJobs is the number of parallel workers to spawn
// when iterating the storage trie.
checkJobs
=
64
// BalanceSlot is an ordinal used to represent slots corresponding to OVM_ETH
// balances in the state.
BalanceSlot
=
1
// AllowanceSlot is an ordinal used to represent slots corresponding to OVM_ETH
// allowances in the state.
AllowanceSlot
=
2
)
var
(
// OVMETHAddress is the address of the OVM ETH predeploy.
OVMETHAddress
=
common
.
HexToAddress
(
"0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000"
)
ignoredSlots
=
map
[
common
.
Hash
]
bool
{
// Total Supply
common
.
HexToHash
(
"0x0000000000000000000000000000000000000000000000000000000000000002"
)
:
true
,
// Name
common
.
HexToHash
(
"0x0000000000000000000000000000000000000000000000000000000000000003"
)
:
true
,
// Symbol
common
.
HexToHash
(
"0x0000000000000000000000000000000000000000000000000000000000000004"
)
:
true
,
common
.
HexToHash
(
"0x0000000000000000000000000000000000000000000000000000000000000005"
)
:
true
,
common
.
HexToHash
(
"0x0000000000000000000000000000000000000000000000000000000000000006"
)
:
true
,
}
// sequencerEntrypointAddr is the address of the OVM sequencer entrypoint contract.
sequencerEntrypointAddr
=
common
.
HexToAddress
(
"0x4200000000000000000000000000000000000005"
)
)
// accountData is a wrapper struct that contains the balance and address of an account.
// It gets passed via channel to the collector process.
type
accountData
struct
{
balance
*
big
.
Int
legacySlot
common
.
Hash
address
common
.
Address
}
// MigrateBalances migrates all balances in the LegacyERC20ETH contract into state. It performs checks
// in parallel with mutations in order to reduce overall migration time.
func
MigrateBalances
(
mutableDB
*
state
.
StateDB
,
dbFactory
util
.
DBFactory
,
addresses
[]
common
.
Address
,
allowances
[]
*
crossdomain
.
Allowance
,
chainID
int
,
noCheck
bool
)
error
{
// Chain params to use for integrity checking.
params
:=
crossdomain
.
ParamsByChainID
[
chainID
]
if
params
==
nil
{
return
fmt
.
Errorf
(
"no chain params for %d"
,
chainID
)
}
return
doMigration
(
mutableDB
,
dbFactory
,
addresses
,
allowances
,
params
.
ExpectedSupplyDelta
,
noCheck
)
}
func
doMigration
(
mutableDB
*
state
.
StateDB
,
dbFactory
util
.
DBFactory
,
addresses
[]
common
.
Address
,
allowances
[]
*
crossdomain
.
Allowance
,
expDiff
*
big
.
Int
,
noCheck
bool
)
error
{
// We'll need to maintain a list of all addresses that we've seen along with all of the storage
// slots based on the witness data.
slotsAddrs
:=
make
(
map
[
common
.
Hash
]
common
.
Address
)
slotsInp
:=
make
(
map
[
common
.
Hash
]
int
)
// For each known address, compute its balance key and add it to the list of addresses.
// Mint events are instrumented as regular ETH events in the witness data, so we no longer
// need to iterate over mint events during the migration.
for
_
,
addr
:=
range
addresses
{
sk
:=
CalcOVMETHStorageKey
(
addr
)
slotsAddrs
[
sk
]
=
addr
slotsInp
[
sk
]
=
BalanceSlot
}
// For each known allowance, compute its storage key and add it to the list of addresses.
for
_
,
allowance
:=
range
allowances
{
sk
:=
CalcAllowanceStorageKey
(
allowance
.
From
,
allowance
.
To
)
slotsAddrs
[
sk
]
=
allowance
.
From
slotsInp
[
sk
]
=
AllowanceSlot
}
// Add the old SequencerEntrypoint because someone sent it ETH a long time ago and it has a
// balance but none of our instrumentation could easily find it. Special case.
entrySK
:=
CalcOVMETHStorageKey
(
sequencerEntrypointAddr
)
slotsAddrs
[
entrySK
]
=
sequencerEntrypointAddr
slotsInp
[
entrySK
]
=
BalanceSlot
// Channel to receive storage slot keys and values from each iteration job.
outCh
:=
make
(
chan
accountData
)
// Channel that gets closed when the collector is done.
doneCh
:=
make
(
chan
struct
{})
// Create a map of accounts we've seen so that we can filter out duplicates.
seenAccounts
:=
make
(
map
[
common
.
Address
]
bool
)
// Keep track of the total migrated supply.
totalFound
:=
new
(
big
.
Int
)
// Kick off a background process to collect
// values from the channel and add them to the map.
var
count
int
var
dups
int
progress
:=
util
.
ProgressLogger
(
1000
,
"Migrated OVM_ETH storage slot"
)
go
func
()
{
defer
func
()
{
doneCh
<-
struct
{}{}
}()
for
account
:=
range
outCh
{
progress
()
// Filter out duplicate accounts. See the below note about keyspace iteration for
// why we may have to filter out duplicates.
if
seenAccounts
[
account
.
address
]
{
log
.
Info
(
"skipping duplicate account during iteration"
,
"addr"
,
account
.
address
)
dups
++
continue
}
// Accumulate addresses and total supply.
totalFound
=
new
(
big
.
Int
)
.
Add
(
totalFound
,
account
.
balance
)
mutableDB
.
SetBalance
(
account
.
address
,
account
.
balance
)
mutableDB
.
SetState
(
predeploys
.
LegacyERC20ETHAddr
,
account
.
legacySlot
,
common
.
Hash
{})
count
++
seenAccounts
[
account
.
address
]
=
true
}
}()
err
:=
util
.
IterateState
(
dbFactory
,
predeploys
.
LegacyERC20ETHAddr
,
func
(
db
*
state
.
StateDB
,
key
,
value
common
.
Hash
)
error
{
// We can safely ignore specific slots (totalSupply, name, symbol).
if
ignoredSlots
[
key
]
{
return
nil
}
slotType
,
ok
:=
slotsInp
[
key
]
if
!
ok
{
log
.
Error
(
"unknown storage slot in state"
,
"slot"
,
key
.
String
())
if
!
noCheck
{
return
fmt
.
Errorf
(
"unknown storage slot in state: %s"
,
key
.
String
())
}
}
// No accounts should have a balance in state. If they do, bail.
addr
,
ok
:=
slotsAddrs
[
key
]
if
!
ok
{
log
.
Crit
(
"could not find address in map - should never happen"
)
}
bal
:=
db
.
GetBalance
(
addr
)
if
bal
.
Sign
()
!=
0
{
log
.
Error
(
"account has non-zero balance in state - should never happen"
,
"addr"
,
addr
,
"balance"
,
bal
.
String
(),
)
if
!
noCheck
{
return
fmt
.
Errorf
(
"account has non-zero balance in state - should never happen: %s"
,
addr
.
String
())
}
}
// Add balances to the total found.
switch
slotType
{
case
BalanceSlot
:
// Send the data to the channel.
outCh
<-
accountData
{
balance
:
value
.
Big
(),
legacySlot
:
key
,
address
:
addr
,
}
case
AllowanceSlot
:
// Allowance slot. Do nothing here.
default
:
// Should never happen.
if
noCheck
{
log
.
Error
(
"unknown slot type"
,
"slot"
,
key
,
"type"
,
slotType
)
}
else
{
log
.
Crit
(
"unknown slot type, should never happen"
,
"type"
,
slotType
)
}
}
return
nil
},
checkJobs
)
if
err
!=
nil
{
return
err
}
// Close the outCh to cancel the collector. The collector will signal that it's done
// using doneCh. Any values waiting to be read from outCh will be read before the
// collector exits.
close
(
outCh
)
<-
doneCh
// Log how many slots were iterated over.
log
.
Info
(
"Iterated legacy balances"
,
"count"
,
count
,
"dups"
,
dups
,
"total"
,
count
+
dups
)
log
.
Info
(
"Comparison to input list of legacy accounts"
,
"total_input"
,
len
(
addresses
),
"diff_count"
,
len
(
addresses
)
-
count
,
"diff_total"
,
len
(
addresses
)
-
(
count
+
dups
),
)
// Print first 10 accounts without balance
aleft
:=
10
log
.
Info
(
"Listing first accounts without balance"
,
"num"
,
aleft
)
for
i
,
a
:=
range
addresses
{
if
!
seenAccounts
[
a
]
{
log
.
Info
(
"Account without balance"
,
"idx"
,
i
,
"addr"
,
a
)
aleft
--
}
if
aleft
==
0
{
break
}
}
// Verify the supply delta. Recorded total supply in the LegacyERC20ETH contract may be higher
// than the actual migrated amount because self-destructs will remove ETH supply in a way that
// cannot be reflected in the contract. This is fine because self-destructs just mean the L2 is
// actually *overcollateralized* by some tiny amount.
db
,
err
:=
dbFactory
()
if
err
!=
nil
{
log
.
Crit
(
"cannot get database"
,
"err"
,
err
)
}
totalSupply
:=
getOVMETHTotalSupply
(
db
)
delta
:=
new
(
big
.
Int
)
.
Sub
(
totalSupply
,
totalFound
)
if
delta
.
Cmp
(
expDiff
)
!=
0
{
log
.
Error
(
"supply mismatch"
,
"migrated"
,
totalFound
.
String
(),
"supply"
,
totalSupply
.
String
(),
"delta"
,
delta
.
String
(),
"exp_delta"
,
expDiff
.
String
(),
)
if
!
noCheck
{
return
fmt
.
Errorf
(
"supply mismatch: %s"
,
delta
.
String
())
}
}
// Supply is verified.
log
.
Info
(
"supply verified OK"
,
"migrated"
,
totalFound
.
String
(),
"supply"
,
totalSupply
.
String
(),
"delta"
,
delta
.
String
(),
"exp_delta"
,
expDiff
.
String
(),
)
// Set the total supply to 0. We do this because the total supply is necessarily going to be
// different than the sum of all balances since we no longer track balances inside the contract
// itself. The total supply is going to be weird no matter what, might as well set it to zero
// so it's explicitly weird instead of implicitly weird.
mutableDB
.
SetState
(
predeploys
.
LegacyERC20ETHAddr
,
getOVMETHTotalSupplySlot
(),
common
.
Hash
{})
log
.
Info
(
"Set the totalSupply to 0"
)
return
nil
}
op-chain-ops/ether/migrate_test.go
deleted
100644 → 0
View file @
cedeb4ec
package
ether
import
(
"math/big"
"math/rand"
"testing"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/require"
)
func
TestMigrateBalances
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
totalSupply
*
big
.
Int
expDiff
*
big
.
Int
stateBalances
map
[
common
.
Address
]
*
big
.
Int
stateAllowances
map
[
common
.
Address
]
common
.
Address
inputAddresses
[]
common
.
Address
inputAllowances
[]
*
crossdomain
.
Allowance
check
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
}{
{
name
:
"everything matches"
,
totalSupply
:
big
.
NewInt
(
3
),
expDiff
:
big
.
NewInt
(
0
),
stateBalances
:
map
[
common
.
Address
]
*
big
.
Int
{
common
.
HexToAddress
(
"0x123"
)
:
big
.
NewInt
(
1
),
common
.
HexToAddress
(
"0x456"
)
:
big
.
NewInt
(
2
),
},
stateAllowances
:
map
[
common
.
Address
]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
)
:
common
.
HexToAddress
(
"0x456"
),
},
inputAddresses
:
[]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
),
common
.
HexToAddress
(
"0x456"
),
},
inputAllowances
:
[]
*
crossdomain
.
Allowance
{
{
From
:
common
.
HexToAddress
(
"0x123"
),
To
:
common
.
HexToAddress
(
"0x456"
),
},
},
check
:
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
{
require
.
NoError
(
t
,
err
)
require
.
EqualValues
(
t
,
common
.
Big1
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x123"
)))
require
.
EqualValues
(
t
,
common
.
Big2
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x456"
)))
require
.
EqualValues
(
t
,
common
.
Hash
{},
db
.
GetState
(
predeploys
.
LegacyERC20ETHAddr
,
GetOVMETHTotalSupplySlot
()))
},
},
{
name
:
"extra input addresses"
,
totalSupply
:
big
.
NewInt
(
1
),
expDiff
:
big
.
NewInt
(
0
),
stateBalances
:
map
[
common
.
Address
]
*
big
.
Int
{
common
.
HexToAddress
(
"0x123"
)
:
big
.
NewInt
(
1
),
},
inputAddresses
:
[]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
),
common
.
HexToAddress
(
"0x456"
),
},
check
:
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
{
require
.
NoError
(
t
,
err
)
require
.
EqualValues
(
t
,
common
.
Big1
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x123"
)))
require
.
EqualValues
(
t
,
common
.
Big0
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x456"
)))
require
.
EqualValues
(
t
,
common
.
Hash
{},
db
.
GetState
(
predeploys
.
LegacyERC20ETHAddr
,
GetOVMETHTotalSupplySlot
()))
},
},
{
name
:
"extra input allowances"
,
totalSupply
:
big
.
NewInt
(
1
),
expDiff
:
big
.
NewInt
(
0
),
stateBalances
:
map
[
common
.
Address
]
*
big
.
Int
{
common
.
HexToAddress
(
"0x123"
)
:
big
.
NewInt
(
1
),
},
stateAllowances
:
map
[
common
.
Address
]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
)
:
common
.
HexToAddress
(
"0x456"
),
},
inputAddresses
:
[]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
),
common
.
HexToAddress
(
"0x456"
),
},
inputAllowances
:
[]
*
crossdomain
.
Allowance
{
{
From
:
common
.
HexToAddress
(
"0x123"
),
To
:
common
.
HexToAddress
(
"0x456"
),
},
{
From
:
common
.
HexToAddress
(
"0x123"
),
To
:
common
.
HexToAddress
(
"0x789"
),
},
},
check
:
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
{
require
.
NoError
(
t
,
err
)
require
.
EqualValues
(
t
,
common
.
Big1
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x123"
)))
require
.
EqualValues
(
t
,
common
.
Big0
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x456"
)))
require
.
EqualValues
(
t
,
common
.
Hash
{},
db
.
GetState
(
predeploys
.
LegacyERC20ETHAddr
,
GetOVMETHTotalSupplySlot
()))
},
},
{
name
:
"missing input addresses"
,
totalSupply
:
big
.
NewInt
(
2
),
expDiff
:
big
.
NewInt
(
0
),
stateBalances
:
map
[
common
.
Address
]
*
big
.
Int
{
common
.
HexToAddress
(
"0x123"
)
:
big
.
NewInt
(
1
),
common
.
HexToAddress
(
"0x456"
)
:
big
.
NewInt
(
1
),
},
inputAddresses
:
[]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
),
},
check
:
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
{
require
.
Error
(
t
,
err
)
require
.
ErrorContains
(
t
,
err
,
"unknown storage slot"
)
},
},
{
name
:
"missing input allowances"
,
totalSupply
:
big
.
NewInt
(
2
),
expDiff
:
big
.
NewInt
(
0
),
stateBalances
:
map
[
common
.
Address
]
*
big
.
Int
{
common
.
HexToAddress
(
"0x123"
)
:
big
.
NewInt
(
1
),
},
stateAllowances
:
map
[
common
.
Address
]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
)
:
common
.
HexToAddress
(
"0x456"
),
common
.
HexToAddress
(
"0x123"
)
:
common
.
HexToAddress
(
"0x789"
),
},
inputAddresses
:
[]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
),
},
inputAllowances
:
[]
*
crossdomain
.
Allowance
{
{
From
:
common
.
HexToAddress
(
"0x123"
),
To
:
common
.
HexToAddress
(
"0x456"
),
},
},
check
:
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
{
require
.
Error
(
t
,
err
)
require
.
ErrorContains
(
t
,
err
,
"unknown storage slot"
)
},
},
{
name
:
"bad supply diff"
,
totalSupply
:
big
.
NewInt
(
4
),
expDiff
:
big
.
NewInt
(
0
),
stateBalances
:
map
[
common
.
Address
]
*
big
.
Int
{
common
.
HexToAddress
(
"0x123"
)
:
big
.
NewInt
(
1
),
common
.
HexToAddress
(
"0x456"
)
:
big
.
NewInt
(
2
),
},
inputAddresses
:
[]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
),
common
.
HexToAddress
(
"0x456"
),
},
check
:
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
{
require
.
Error
(
t
,
err
)
require
.
ErrorContains
(
t
,
err
,
"supply mismatch"
)
},
},
{
name
:
"good supply diff"
,
totalSupply
:
big
.
NewInt
(
4
),
expDiff
:
big
.
NewInt
(
1
),
stateBalances
:
map
[
common
.
Address
]
*
big
.
Int
{
common
.
HexToAddress
(
"0x123"
)
:
big
.
NewInt
(
1
),
common
.
HexToAddress
(
"0x456"
)
:
big
.
NewInt
(
2
),
},
inputAddresses
:
[]
common
.
Address
{
common
.
HexToAddress
(
"0x123"
),
common
.
HexToAddress
(
"0x456"
),
},
check
:
func
(
t
*
testing
.
T
,
db
*
state
.
StateDB
,
err
error
)
{
require
.
NoError
(
t
,
err
)
require
.
EqualValues
(
t
,
common
.
Big1
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x123"
)))
require
.
EqualValues
(
t
,
common
.
Big2
,
db
.
GetBalance
(
common
.
HexToAddress
(
"0x456"
)))
},
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
db
,
factory
:=
makeLegacyETH
(
t
,
tt
.
totalSupply
,
tt
.
stateBalances
,
tt
.
stateAllowances
)
err
:=
doMigration
(
db
,
factory
,
tt
.
inputAddresses
,
tt
.
inputAllowances
,
tt
.
expDiff
,
false
)
tt
.
check
(
t
,
db
,
err
)
})
}
}
func
makeLegacyETH
(
t
*
testing
.
T
,
totalSupply
*
big
.
Int
,
balances
map
[
common
.
Address
]
*
big
.
Int
,
allowances
map
[
common
.
Address
]
common
.
Address
)
(
*
state
.
StateDB
,
util
.
DBFactory
)
{
memDB
:=
rawdb
.
NewMemoryDatabase
()
db
,
err
:=
state
.
New
(
common
.
Hash
{},
state
.
NewDatabaseWithConfig
(
memDB
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
}),
nil
)
require
.
NoError
(
t
,
err
)
db
.
CreateAccount
(
OVMETHAddress
)
db
.
SetState
(
OVMETHAddress
,
getOVMETHTotalSupplySlot
(),
common
.
BigToHash
(
totalSupply
))
for
slot
:=
range
ignoredSlots
{
if
slot
==
getOVMETHTotalSupplySlot
()
{
continue
}
db
.
SetState
(
OVMETHAddress
,
slot
,
common
.
Hash
{
31
:
0xff
})
}
for
addr
,
balance
:=
range
balances
{
db
.
SetState
(
OVMETHAddress
,
CalcOVMETHStorageKey
(
addr
),
common
.
BigToHash
(
balance
))
}
for
from
,
to
:=
range
allowances
{
db
.
SetState
(
OVMETHAddress
,
CalcAllowanceStorageKey
(
from
,
to
),
common
.
BigToHash
(
big
.
NewInt
(
1
)))
}
root
,
err
:=
db
.
Commit
(
false
)
require
.
NoError
(
t
,
err
)
err
=
db
.
Database
()
.
TrieDB
()
.
Commit
(
root
,
true
)
require
.
NoError
(
t
,
err
)
return
db
,
func
()
(
*
state
.
StateDB
,
error
)
{
return
state
.
New
(
root
,
state
.
NewDatabaseWithConfig
(
memDB
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
}),
nil
)
}
}
// TestMigrateBalancesRandomOK tests that the pre-check balances function works
// with random addresses. This test makes sure that the partition logic doesn't
// miss anything, and helps detect concurrency errors.
func
TestMigrateBalancesRandomOK
(
t
*
testing
.
T
)
{
for
i
:=
0
;
i
<
100
;
i
++
{
addresses
,
stateBalances
,
allowances
,
stateAllowances
,
totalSupply
:=
setupRandTest
(
t
)
db
,
factory
:=
makeLegacyETH
(
t
,
totalSupply
,
stateBalances
,
stateAllowances
)
err
:=
doMigration
(
db
,
factory
,
addresses
,
allowances
,
big
.
NewInt
(
0
),
false
)
require
.
NoError
(
t
,
err
)
for
addr
,
expBal
:=
range
stateBalances
{
actBal
:=
db
.
GetBalance
(
addr
)
require
.
EqualValues
(
t
,
expBal
,
actBal
)
}
}
}
// TestMigrateBalancesRandomMissing tests that the pre-check balances function works
// with random addresses when some of them are missing. This helps make sure that the
// partition logic doesn't miss anything, and helps detect concurrency errors.
func
TestMigrateBalancesRandomMissing
(
t
*
testing
.
T
)
{
for
i
:=
0
;
i
<
100
;
i
++
{
addresses
,
stateBalances
,
allowances
,
stateAllowances
,
totalSupply
:=
setupRandTest
(
t
)
if
len
(
addresses
)
==
0
{
continue
}
// Remove a random address from the list of witnesses
idx
:=
rand
.
Intn
(
len
(
addresses
))
addresses
=
append
(
addresses
[
:
idx
],
addresses
[
idx
+
1
:
]
...
)
db
,
factory
:=
makeLegacyETH
(
t
,
totalSupply
,
stateBalances
,
stateAllowances
)
err
:=
doMigration
(
db
,
factory
,
addresses
,
allowances
,
big
.
NewInt
(
0
),
false
)
require
.
ErrorContains
(
t
,
err
,
"unknown storage slot"
)
}
for
i
:=
0
;
i
<
100
;
i
++
{
addresses
,
stateBalances
,
allowances
,
stateAllowances
,
totalSupply
:=
setupRandTest
(
t
)
if
len
(
allowances
)
==
0
{
continue
}
// Remove a random allowance from the list of witnesses
idx
:=
rand
.
Intn
(
len
(
allowances
))
allowances
=
append
(
allowances
[
:
idx
],
allowances
[
idx
+
1
:
]
...
)
db
,
factory
:=
makeLegacyETH
(
t
,
totalSupply
,
stateBalances
,
stateAllowances
)
err
:=
doMigration
(
db
,
factory
,
addresses
,
allowances
,
big
.
NewInt
(
0
),
false
)
require
.
ErrorContains
(
t
,
err
,
"unknown storage slot"
)
}
}
func
randAddr
(
t
*
testing
.
T
)
common
.
Address
{
var
addr
common
.
Address
_
,
err
:=
rand
.
Read
(
addr
[
:
])
require
.
NoError
(
t
,
err
)
return
addr
}
func
setupRandTest
(
t
*
testing
.
T
)
([]
common
.
Address
,
map
[
common
.
Address
]
*
big
.
Int
,
[]
*
crossdomain
.
Allowance
,
map
[
common
.
Address
]
common
.
Address
,
*
big
.
Int
)
{
addresses
:=
make
([]
common
.
Address
,
0
)
stateBalances
:=
make
(
map
[
common
.
Address
]
*
big
.
Int
)
allowances
:=
make
([]
*
crossdomain
.
Allowance
,
0
)
stateAllowances
:=
make
(
map
[
common
.
Address
]
common
.
Address
)
totalSupply
:=
big
.
NewInt
(
0
)
for
j
:=
0
;
j
<
rand
.
Intn
(
10000
);
j
++
{
addr
:=
randAddr
(
t
)
addresses
=
append
(
addresses
,
addr
)
stateBalances
[
addr
]
=
big
.
NewInt
(
int64
(
rand
.
Intn
(
1
_000_000
)))
totalSupply
=
new
(
big
.
Int
)
.
Add
(
totalSupply
,
stateBalances
[
addr
])
}
for
j
:=
0
;
j
<
rand
.
Intn
(
1000
);
j
++
{
addr
:=
randAddr
(
t
)
to
:=
randAddr
(
t
)
allowances
=
append
(
allowances
,
&
crossdomain
.
Allowance
{
From
:
addr
,
To
:
to
,
})
stateAllowances
[
addr
]
=
to
}
return
addresses
,
stateBalances
,
allowances
,
stateAllowances
,
totalSupply
}
op-chain-ops/ether/storage.go
deleted
100644 → 0
View file @
cedeb4ec
package
ether
import
(
"github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
)
// BytesBacked is a re-export of the same interface in Geth,
// which is unfortunately private.
type
BytesBacked
interface
{
Bytes
()
[]
byte
}
// CalcAllowanceStorageKey calculates the storage key of an allowance in OVM ETH.
func
CalcAllowanceStorageKey
(
owner
common
.
Address
,
spender
common
.
Address
)
common
.
Hash
{
inner
:=
CalcStorageKey
(
owner
,
common
.
Big1
)
return
CalcStorageKey
(
spender
,
inner
)
}
// CalcOVMETHStorageKey calculates the storage key of an OVM ETH balance.
func
CalcOVMETHStorageKey
(
addr
common
.
Address
)
common
.
Hash
{
return
CalcStorageKey
(
addr
,
common
.
Big0
)
}
// CalcStorageKey is a helper method to calculate storage keys.
func
CalcStorageKey
(
a
,
b
BytesBacked
)
common
.
Hash
{
hasher
:=
sha3
.
NewLegacyKeccak256
()
hasher
.
Write
(
common
.
LeftPadBytes
(
a
.
Bytes
(),
32
))
hasher
.
Write
(
common
.
LeftPadBytes
(
b
.
Bytes
(),
32
))
digest
:=
hasher
.
Sum
(
nil
)
return
common
.
BytesToHash
(
digest
)
}
op-chain-ops/genesis/check.go
deleted
100644 → 0
View file @
cedeb4ec
package
genesis
import
(
"bytes"
"encoding/binary"
"errors"
"fmt"
"math/big"
"math/rand"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-chain-ops/ether"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
)
const
(
// MaxPredeploySlotChecks is the maximum number of storage slots to check
// when validating the untouched predeploys. This limit is in place
// to bound execution time of the migration. We can parallelize this
// in the future.
MaxPredeploySlotChecks
=
1000
// MaxOVMETHSlotChecks is the maximum number of OVM ETH storage slots to check
// when validating the OVM ETH migration.
MaxOVMETHSlotChecks
=
5000
// OVMETHSampleLikelihood is the probability that a storage slot will be checked
// when validating the OVM ETH migration.
OVMETHSampleLikelihood
=
0.1
)
type
StorageCheckMap
=
map
[
common
.
Hash
]
common
.
Hash
var
(
L2XDMOwnerSlot
=
common
.
Hash
{
31
:
0x33
}
ProxyAdminOwnerSlot
=
common
.
Hash
{}
LegacyETHCheckSlots
=
map
[
common
.
Hash
]
common
.
Hash
{
// Bridge
common
.
Hash
{
31
:
0x06
}
:
common
.
HexToHash
(
"0x0000000000000000000000004200000000000000000000000000000000000010"
),
// Symbol
common
.
Hash
{
31
:
0x04
}
:
common
.
HexToHash
(
"0x4554480000000000000000000000000000000000000000000000000000000006"
),
// Name
common
.
Hash
{
31
:
0x03
}
:
common
.
HexToHash
(
"0x457468657200000000000000000000000000000000000000000000000000000a"
),
// Total supply
common
.
Hash
{
31
:
0x02
}
:
{},
}
// ExpectedStorageSlots is a map of predeploy addresses to the storage slots and values that are
// expected to be set in those predeploys after the migration. It does not include any predeploys
// that were not wiped. It also accounts for the 2 EIP-1967 storage slots in each contract.
// It does _not_ include L1Block. L1Block is checked separately.
ExpectedStorageSlots
=
map
[
common
.
Address
]
StorageCheckMap
{
predeploys
.
L2CrossDomainMessengerAddr
:
{
// Slot 0x00 (0) is a combination of spacer_0_0_20, _initialized, and _initializing
common
.
Hash
{}
:
common
.
HexToHash
(
"0x0000000000000000000000010000000000000000000000000000000000000000"
),
// Slot 0xcc (204) is xDomainMsgSender
common
.
Hash
{
31
:
0xcc
}
:
common
.
HexToHash
(
"0x000000000000000000000000000000000000000000000000000000000000dead"
),
// EIP-1967 storage slots
AdminSlot
:
common
.
HexToHash
(
"0x0000000000000000000000004200000000000000000000000000000000000018"
),
ImplementationSlot
:
common
.
HexToHash
(
"0x000000000000000000000000c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30007"
),
},
predeploys
.
L2StandardBridgeAddr
:
eip1967Slots
(
predeploys
.
L2StandardBridgeAddr
),
predeploys
.
SequencerFeeVaultAddr
:
eip1967Slots
(
predeploys
.
SequencerFeeVaultAddr
),
predeploys
.
OptimismMintableERC20FactoryAddr
:
eip1967Slots
(
predeploys
.
OptimismMintableERC20FactoryAddr
),
predeploys
.
L1BlockNumberAddr
:
eip1967Slots
(
predeploys
.
L1BlockNumberAddr
),
predeploys
.
GasPriceOracleAddr
:
eip1967Slots
(
predeploys
.
GasPriceOracleAddr
),
//predeploys.L1BlockAddr: eip1967Slots(predeploys.L1BlockAddr),
predeploys
.
L2ERC721BridgeAddr
:
eip1967Slots
(
predeploys
.
L2ERC721BridgeAddr
),
predeploys
.
OptimismMintableERC721FactoryAddr
:
eip1967Slots
(
predeploys
.
OptimismMintableERC721FactoryAddr
),
// ProxyAdmin is not a proxy, and only has the _owner slot set.
predeploys
.
ProxyAdminAddr
:
{
// Slot 0x00 (0) is _owner. Requires custom check, so set to a garbage value
ProxyAdminOwnerSlot
:
common
.
HexToHash
(
"0xbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad0"
),
// EIP-1967 storage slots
AdminSlot
:
common
.
HexToHash
(
"0x0000000000000000000000004200000000000000000000000000000000000018"
),
ImplementationSlot
:
common
.
HexToHash
(
"0x000000000000000000000000c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30018"
),
},
predeploys
.
BaseFeeVaultAddr
:
eip1967Slots
(
predeploys
.
BaseFeeVaultAddr
),
predeploys
.
L1FeeVaultAddr
:
eip1967Slots
(
predeploys
.
L1FeeVaultAddr
),
}
)
// PostCheckMigratedDB will check that the migration was performed correctly
func
PostCheckMigratedDB
(
ldb
ethdb
.
Database
,
migrationData
crossdomain
.
MigrationData
,
l1XDM
*
common
.
Address
,
l1ChainID
uint64
,
l2ChainID
uint64
,
finalSystemOwner
common
.
Address
,
proxyAdminOwner
common
.
Address
,
info
*
derive
.
L1BlockInfo
,
)
error
{
log
.
Info
(
"Validating database migration"
)
hash
:=
rawdb
.
ReadHeadHeaderHash
(
ldb
)
log
.
Info
(
"Reading chain tip from database"
,
"hash"
,
hash
)
num
:=
rawdb
.
ReadHeaderNumber
(
ldb
,
hash
)
if
num
==
nil
{
return
fmt
.
Errorf
(
"cannot find header number for %s"
,
hash
)
}
header
:=
rawdb
.
ReadHeader
(
ldb
,
hash
,
*
num
)
log
.
Info
(
"Read header from database"
,
"number"
,
*
num
)
if
!
bytes
.
Equal
(
header
.
Extra
,
BedrockTransitionBlockExtraData
)
{
return
fmt
.
Errorf
(
"expected extra data to be %x, but got %x"
,
BedrockTransitionBlockExtraData
,
header
.
Extra
)
}
prevHeader
:=
rawdb
.
ReadHeader
(
ldb
,
header
.
ParentHash
,
*
num
-
1
)
log
.
Info
(
"Read previous header from database"
,
"number"
,
*
num
-
1
)
underlyingDB
:=
state
.
NewDatabaseWithConfig
(
ldb
,
&
trie
.
Config
{
Preimages
:
true
,
})
prevDB
,
err
:=
state
.
New
(
prevHeader
.
Root
,
underlyingDB
,
nil
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot open historical StateDB: %w"
,
err
)
}
db
,
err
:=
state
.
New
(
header
.
Root
,
underlyingDB
,
nil
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot open StateDB: %w"
,
err
)
}
if
err
:=
PostCheckPredeployStorage
(
db
,
finalSystemOwner
,
proxyAdminOwner
);
err
!=
nil
{
return
err
}
log
.
Info
(
"checked predeploy storage"
)
if
err
:=
PostCheckUntouchables
(
underlyingDB
,
db
,
prevHeader
.
Root
,
l1ChainID
);
err
!=
nil
{
return
err
}
log
.
Info
(
"checked untouchables"
)
if
err
:=
PostCheckPredeploys
(
prevDB
,
db
);
err
!=
nil
{
return
err
}
log
.
Info
(
"checked predeploys"
)
if
err
:=
PostCheckL1Block
(
db
,
info
);
err
!=
nil
{
return
err
}
log
.
Info
(
"checked L1Block"
)
if
err
:=
PostCheckLegacyETH
(
prevDB
,
db
,
migrationData
);
err
!=
nil
{
return
err
}
log
.
Info
(
"checked legacy eth"
)
if
err
:=
CheckWithdrawalsAfter
(
db
,
migrationData
,
l1XDM
,
new
(
big
.
Int
)
.
SetUint64
(
l2ChainID
));
err
!=
nil
{
return
err
}
log
.
Info
(
"checked withdrawals"
)
return
nil
}
// PostCheckUntouchables will check that the untouchable contracts have
// not been modified by the migration process.
func
PostCheckUntouchables
(
udb
state
.
Database
,
currDB
*
state
.
StateDB
,
prevRoot
common
.
Hash
,
l1ChainID
uint64
)
error
{
prevDB
,
err
:=
state
.
New
(
prevRoot
,
udb
,
nil
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot open StateDB: %w"
,
err
)
}
for
addr
:=
range
UntouchablePredeploys
{
// Check that the code is the same.
code
:=
currDB
.
GetCode
(
addr
)
hash
:=
crypto
.
Keccak256Hash
(
code
)
expHash
:=
UntouchableCodeHashes
[
addr
][
l1ChainID
]
if
hash
!=
expHash
{
return
fmt
.
Errorf
(
"expected code hash for %s to be %s, but got %s"
,
addr
,
expHash
,
hash
)
}
log
.
Info
(
"checked code hash"
,
"address"
,
addr
,
"hash"
,
hash
)
// Ensure that the current/previous roots match
var
prevRoot
,
currRoot
common
.
Hash
prevStorage
,
err
:=
prevDB
.
StorageTrie
(
addr
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to open previous-db storage trie of %s: %w"
,
addr
,
err
)
}
if
prevStorage
==
nil
{
prevRoot
=
types
.
EmptyRootHash
}
else
{
prevRoot
=
prevStorage
.
Hash
()
}
currStorage
,
err
:=
currDB
.
StorageTrie
(
addr
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to open current-db storage trie of %s: %w"
,
addr
,
err
)
}
if
currStorage
==
nil
{
currRoot
=
types
.
EmptyRootHash
}
else
{
currRoot
=
currStorage
.
Hash
()
}
if
prevRoot
!=
currRoot
{
return
fmt
.
Errorf
(
"expected storage root for %s to be %s, but got %s"
,
addr
,
prevRoot
,
currRoot
)
}
log
.
Info
(
"checked account roots"
,
"address"
,
addr
,
"curr_root"
,
currRoot
,
"prev_root"
,
prevRoot
)
// Sample storage slots to ensure that they are not modified.
var
count
int
expSlots
:=
make
(
map
[
common
.
Hash
]
common
.
Hash
)
if
err
:=
prevDB
.
ForEachStorage
(
addr
,
func
(
key
,
value
common
.
Hash
)
bool
{
count
++
expSlots
[
key
]
=
value
return
count
<
MaxPredeploySlotChecks
});
err
!=
nil
{
return
fmt
.
Errorf
(
"error iterating over storage: %w"
,
err
)
}
for
expKey
,
expValue
:=
range
expSlots
{
actValue
:=
currDB
.
GetState
(
addr
,
expKey
)
if
actValue
!=
expValue
{
return
fmt
.
Errorf
(
"expected slot %s on %s to be %s, but got %s"
,
expKey
,
addr
,
expValue
,
actValue
)
}
}
log
.
Info
(
"checked storage"
,
"address"
,
addr
,
"count"
,
count
)
}
return
nil
}
// PostCheckPredeploys will check that there is code at each predeploy
// address
func
PostCheckPredeploys
(
prevDB
,
currDB
*
state
.
StateDB
)
error
{
for
i
:=
uint64
(
0
);
i
<=
2048
;
i
++
{
// Compute the predeploy address
bigAddr
:=
new
(
big
.
Int
)
.
Or
(
bigL2PredeployNamespace
,
new
(
big
.
Int
)
.
SetUint64
(
i
))
addr
:=
common
.
BigToAddress
(
bigAddr
)
// Get the code for the predeploy
code
:=
currDB
.
GetCode
(
addr
)
// There must be code for the predeploy
if
len
(
code
)
==
0
{
return
fmt
.
Errorf
(
"no code found at predeploy %s"
,
addr
)
}
if
UntouchablePredeploys
[
addr
]
{
log
.
Trace
(
"skipping untouchable predeploy"
,
"address"
,
addr
)
continue
}
// There must be an admin
admin
:=
currDB
.
GetState
(
addr
,
AdminSlot
)
adminAddr
:=
common
.
BytesToAddress
(
admin
.
Bytes
())
if
addr
!=
predeploys
.
ProxyAdminAddr
&&
addr
!=
predeploys
.
GovernanceTokenAddr
&&
adminAddr
!=
predeploys
.
ProxyAdminAddr
{
return
fmt
.
Errorf
(
"expected admin for %s to be %s but got %s"
,
addr
,
predeploys
.
ProxyAdminAddr
,
adminAddr
)
}
// Balances and nonces should match legacy
oldNonce
:=
prevDB
.
GetNonce
(
addr
)
oldBalance
:=
ether
.
GetOVMETHBalance
(
prevDB
,
addr
)
newNonce
:=
currDB
.
GetNonce
(
addr
)
newBalance
:=
currDB
.
GetBalance
(
addr
)
if
oldNonce
!=
newNonce
{
return
fmt
.
Errorf
(
"expected nonce for %s to be %d but got %d"
,
addr
,
oldNonce
,
newNonce
)
}
if
oldBalance
.
Cmp
(
newBalance
)
!=
0
{
return
fmt
.
Errorf
(
"expected balance for %s to be %d but got %d"
,
addr
,
oldBalance
,
newBalance
)
}
}
// For each predeploy, check that we've set the implementation correctly when
// necessary and that there's code at the implementation.
for
_
,
proxyAddr
:=
range
predeploys
.
Predeploys
{
if
UntouchablePredeploys
[
*
proxyAddr
]
{
log
.
Trace
(
"skipping untouchable predeploy"
,
"address"
,
proxyAddr
)
continue
}
if
*
proxyAddr
==
predeploys
.
LegacyERC20ETHAddr
{
log
.
Trace
(
"skipping legacy eth predeploy"
)
continue
}
if
*
proxyAddr
==
predeploys
.
ProxyAdminAddr
{
implCode
:=
currDB
.
GetCode
(
*
proxyAddr
)
if
len
(
implCode
)
==
0
{
return
errors
.
New
(
"no code found at proxy admin"
)
}
continue
}
expImplAddr
,
err
:=
AddressToCodeNamespace
(
*
proxyAddr
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"error converting to code namespace: %w"
,
err
)
}
implCode
:=
currDB
.
GetCode
(
expImplAddr
)
if
len
(
implCode
)
==
0
{
return
fmt
.
Errorf
(
"no code found at predeploy impl %s"
,
*
proxyAddr
)
}
impl
:=
currDB
.
GetState
(
*
proxyAddr
,
ImplementationSlot
)
actImplAddr
:=
common
.
BytesToAddress
(
impl
.
Bytes
())
if
expImplAddr
!=
actImplAddr
{
return
fmt
.
Errorf
(
"expected implementation for %s to be at %s, but got %s"
,
*
proxyAddr
,
expImplAddr
,
actImplAddr
)
}
}
return
nil
}
// PostCheckPredeployStorage will ensure that the predeploys had their storage
// wiped correctly.
func
PostCheckPredeployStorage
(
db
*
state
.
StateDB
,
finalSystemOwner
common
.
Address
,
proxyAdminOwner
common
.
Address
)
error
{
for
name
,
addr
:=
range
predeploys
.
Predeploys
{
if
addr
==
nil
{
return
fmt
.
Errorf
(
"nil address in predeploys mapping for %s"
,
name
)
}
// Skip the addresses that did not have their storage reset, also skip the
// L2ToL1MessagePasser because it's already covered by the withdrawals check.
if
FrozenStoragePredeploys
[
*
addr
]
||
*
addr
==
predeploys
.
L2ToL1MessagePasserAddr
||
*
addr
==
predeploys
.
L1BlockAddr
{
continue
}
// Create a mapping of all storage slots. These values were wiped
// so it should not take long to iterate through all of them.
slots
:=
make
(
map
[
common
.
Hash
]
common
.
Hash
)
err
:=
db
.
ForEachStorage
(
*
addr
,
func
(
key
,
value
common
.
Hash
)
bool
{
slots
[
key
]
=
value
return
true
})
if
err
!=
nil
{
return
err
}
log
.
Info
(
"predeploy storage"
,
"name"
,
name
,
"address"
,
*
addr
,
"count"
,
len
(
slots
))
for
key
,
value
:=
range
slots
{
log
.
Debug
(
"storage values"
,
"key"
,
key
.
String
(),
"value"
,
value
.
String
())
}
expSlots
:=
ExpectedStorageSlots
[
*
addr
]
// Assert that the correct number of slots are present.
if
len
(
expSlots
)
!=
len
(
slots
)
{
return
fmt
.
Errorf
(
"expected %d storage slots for %s but got %d"
,
len
(
expSlots
),
name
,
len
(
slots
))
}
for
key
,
value
:=
range
expSlots
{
// The owner slots for the L2XDM and ProxyAdmin are special cases.
// They are set to the final system owner in the config.
if
*
addr
==
predeploys
.
ProxyAdminAddr
&&
key
==
ProxyAdminOwnerSlot
{
actualOwner
:=
common
.
BytesToAddress
(
slots
[
key
]
.
Bytes
())
if
actualOwner
!=
proxyAdminOwner
{
return
fmt
.
Errorf
(
"expected owner for %s to be %s but got %s"
,
name
,
proxyAdminOwner
,
actualOwner
)
}
log
.
Debug
(
"validated special case owner slot"
,
"value"
,
actualOwner
,
"name"
,
name
)
continue
}
if
slots
[
key
]
!=
value
{
log
.
Debug
(
"validated storage value"
,
"key"
,
key
.
String
(),
"value"
,
value
.
String
())
return
fmt
.
Errorf
(
"expected storage slot %s to be %s but got %s"
,
key
,
value
,
slots
[
key
])
}
}
}
return
nil
}
// PostCheckLegacyETH checks that the legacy eth migration was successful.
// It checks that the total supply was set to 0, and randomly samples storage
// slots pre- and post-migration to ensure that balances were correctly migrated.
func
PostCheckLegacyETH
(
prevDB
,
migratedDB
*
state
.
StateDB
,
migrationData
crossdomain
.
MigrationData
)
error
{
allowanceSlots
:=
make
(
map
[
common
.
Hash
]
bool
)
addresses
:=
make
(
map
[
common
.
Hash
]
common
.
Address
)
log
.
Info
(
"recomputing witness data"
)
for
_
,
allowance
:=
range
migrationData
.
OvmAllowances
{
key
:=
ether
.
CalcAllowanceStorageKey
(
allowance
.
From
,
allowance
.
To
)
allowanceSlots
[
key
]
=
true
}
for
_
,
addr
:=
range
migrationData
.
Addresses
()
{
addresses
[
ether
.
CalcOVMETHStorageKey
(
addr
)]
=
addr
}
log
.
Info
(
"checking legacy eth fixed storage slots"
)
for
slot
,
expValue
:=
range
LegacyETHCheckSlots
{
actValue
:=
migratedDB
.
GetState
(
predeploys
.
LegacyERC20ETHAddr
,
slot
)
if
actValue
!=
expValue
{
return
fmt
.
Errorf
(
"expected slot %s on %s to be %s, but got %s"
,
slot
,
predeploys
.
LegacyERC20ETHAddr
,
expValue
,
actValue
)
}
}
var
count
int
threshold
:=
100
-
int
(
100
*
OVMETHSampleLikelihood
)
progress
:=
util
.
ProgressLogger
(
100
,
"checking legacy eth balance slots"
)
var
innerErr
error
err
:=
prevDB
.
ForEachStorage
(
predeploys
.
LegacyERC20ETHAddr
,
func
(
key
,
value
common
.
Hash
)
bool
{
val
:=
rand
.
Intn
(
100
)
// Randomly sample storage slots.
if
val
>
threshold
{
return
true
}
// Ignore fixed slots.
if
_
,
ok
:=
LegacyETHCheckSlots
[
key
];
ok
{
return
true
}
// Ignore allowances.
if
allowanceSlots
[
key
]
{
return
true
}
// Grab the address, and bail if we can't find it.
addr
,
ok
:=
addresses
[
key
]
if
!
ok
{
innerErr
=
fmt
.
Errorf
(
"unknown OVM_ETH storage slot %s"
,
key
)
return
false
}
// Pull out the pre-migration OVM ETH balance, and the state balance.
ovmETHBalance
:=
value
.
Big
()
ovmETHStateBalance
:=
prevDB
.
GetBalance
(
addr
)
// Pre-migration state balance should be zero.
if
ovmETHStateBalance
.
Cmp
(
common
.
Big0
)
!=
0
{
innerErr
=
fmt
.
Errorf
(
"expected OVM_ETH pre-migration state balance for %s to be 0, but got %s"
,
addr
,
ovmETHStateBalance
)
return
false
}
// Migrated state balance should equal the OVM ETH balance.
migratedStateBalance
:=
migratedDB
.
GetBalance
(
addr
)
if
migratedStateBalance
.
Cmp
(
ovmETHBalance
)
!=
0
{
innerErr
=
fmt
.
Errorf
(
"expected OVM_ETH post-migration state balance for %s to be %s, but got %s"
,
addr
,
ovmETHStateBalance
,
migratedStateBalance
)
return
false
}
// Migrated OVM ETH balance should be zero, since we wipe the slots.
migratedBalance
:=
migratedDB
.
GetState
(
predeploys
.
LegacyERC20ETHAddr
,
key
)
if
migratedBalance
.
Big
()
.
Cmp
(
common
.
Big0
)
!=
0
{
innerErr
=
fmt
.
Errorf
(
"expected OVM_ETH post-migration ERC20 balance for %s to be 0, but got %s"
,
addr
,
migratedBalance
)
return
false
}
progress
()
count
++
// Stop iterating if we've checked enough slots.
return
count
<
MaxOVMETHSlotChecks
})
if
err
!=
nil
{
return
fmt
.
Errorf
(
"error iterating over OVM_ETH storage: %w"
,
err
)
}
if
innerErr
!=
nil
{
return
innerErr
}
return
nil
}
// PostCheckL1Block checks that the L1Block contract was properly set to the L1 origin.
func
PostCheckL1Block
(
db
*
state
.
StateDB
,
info
*
derive
.
L1BlockInfo
)
error
{
// Slot 0 is the concatenation of the block number and timestamp
data
:=
db
.
GetState
(
predeploys
.
L1BlockAddr
,
common
.
Hash
{})
.
Bytes
()
blockNumber
:=
binary
.
BigEndian
.
Uint64
(
data
[
24
:
])
timestamp
:=
binary
.
BigEndian
.
Uint64
(
data
[
16
:
24
])
if
blockNumber
!=
info
.
Number
{
return
fmt
.
Errorf
(
"expected L1Block block number to be %d, but got %d"
,
info
.
Number
,
blockNumber
)
}
log
.
Debug
(
"validated L1Block block number"
,
"expected"
,
info
.
Number
)
if
timestamp
!=
info
.
Time
{
return
fmt
.
Errorf
(
"expected L1Block timestamp to be %d, but got %d"
,
info
.
Time
,
timestamp
)
}
log
.
Debug
(
"validated L1Block timestamp"
,
"expected"
,
info
.
Time
)
// Slot 1 is the basefee.
baseFee
:=
db
.
GetState
(
predeploys
.
L1BlockAddr
,
common
.
Hash
{
31
:
0x01
})
.
Big
()
if
baseFee
.
Cmp
(
info
.
BaseFee
)
!=
0
{
return
fmt
.
Errorf
(
"expected L1Block basefee to be %s, but got %s"
,
info
.
BaseFee
,
baseFee
)
}
log
.
Debug
(
"validated L1Block basefee"
,
"expected"
,
info
.
BaseFee
)
// Slot 2 is the block hash
hash
:=
db
.
GetState
(
predeploys
.
L1BlockAddr
,
common
.
Hash
{
31
:
0x02
})
if
hash
!=
info
.
BlockHash
{
return
fmt
.
Errorf
(
"expected L1Block hash to be %s, but got %s"
,
info
.
BlockHash
,
hash
)
}
log
.
Debug
(
"validated L1Block hash"
,
"expected"
,
info
.
BlockHash
)
// Slot 3 is the sequence number. It is expected to be zero.
sequenceNumber
:=
db
.
GetState
(
predeploys
.
L1BlockAddr
,
common
.
Hash
{
31
:
0x03
})
expSequenceNumber
:=
common
.
Hash
{}
if
expSequenceNumber
!=
sequenceNumber
{
return
fmt
.
Errorf
(
"expected L1Block sequence number to be %s, but got %s"
,
expSequenceNumber
,
sequenceNumber
)
}
log
.
Debug
(
"validated L1Block sequence number"
,
"expected"
,
expSequenceNumber
)
// Slot 4 is the versioned hash to authenticate the batcher. It is expected to be the initial batch sender.
batcherHash
:=
db
.
GetState
(
predeploys
.
L1BlockAddr
,
common
.
Hash
{
31
:
0x04
})
batchSender
:=
common
.
BytesToAddress
(
batcherHash
.
Bytes
())
if
batchSender
!=
info
.
BatcherAddr
{
return
fmt
.
Errorf
(
"expected L1Block batcherHash to be %s, but got %s"
,
info
.
BatcherAddr
,
batchSender
)
}
log
.
Debug
(
"validated L1Block batcherHash"
,
"expected"
,
info
.
BatcherAddr
)
// Slot 5 is the L1 fee overhead.
l1FeeOverhead
:=
db
.
GetState
(
predeploys
.
L1BlockAddr
,
common
.
Hash
{
31
:
0x05
})
if
!
bytes
.
Equal
(
l1FeeOverhead
.
Bytes
(),
info
.
L1FeeOverhead
[
:
])
{
return
fmt
.
Errorf
(
"expected L1Block L1FeeOverhead to be %s, but got %s"
,
info
.
L1FeeOverhead
,
l1FeeOverhead
)
}
log
.
Debug
(
"validated L1Block L1FeeOverhead"
,
"expected"
,
info
.
L1FeeOverhead
)
// Slot 6 is the L1 fee scalar.
l1FeeScalar
:=
db
.
GetState
(
predeploys
.
L1BlockAddr
,
common
.
Hash
{
31
:
0x06
})
if
!
bytes
.
Equal
(
l1FeeScalar
.
Bytes
(),
info
.
L1FeeScalar
[
:
])
{
return
fmt
.
Errorf
(
"expected L1Block L1FeeScalar to be %s, but got %s"
,
info
.
L1FeeScalar
,
l1FeeScalar
)
}
log
.
Debug
(
"validated L1Block L1FeeScalar"
,
"expected"
,
info
.
L1FeeScalar
)
// Check EIP-1967
proxyAdmin
:=
common
.
BytesToAddress
(
db
.
GetState
(
predeploys
.
L1BlockAddr
,
AdminSlot
)
.
Bytes
())
if
proxyAdmin
!=
predeploys
.
ProxyAdminAddr
{
return
fmt
.
Errorf
(
"expected L1Block admin to be %s, but got %s"
,
predeploys
.
ProxyAdminAddr
,
proxyAdmin
)
}
log
.
Debug
(
"validated L1Block admin"
,
"expected"
,
predeploys
.
ProxyAdminAddr
)
expImplementation
,
err
:=
AddressToCodeNamespace
(
predeploys
.
L1BlockAddr
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to get expected implementation for L1Block: %w"
,
err
)
}
actImplementation
:=
common
.
BytesToAddress
(
db
.
GetState
(
predeploys
.
L1BlockAddr
,
ImplementationSlot
)
.
Bytes
())
if
expImplementation
!=
actImplementation
{
return
fmt
.
Errorf
(
"expected L1Block implementation to be %s, but got %s"
,
expImplementation
,
actImplementation
)
}
log
.
Debug
(
"validated L1Block implementation"
,
"expected"
,
expImplementation
)
var
count
int
err
=
db
.
ForEachStorage
(
predeploys
.
L1BlockAddr
,
func
(
key
,
value
common
.
Hash
)
bool
{
count
++
return
true
})
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to iterate over L1Block storage: %w"
,
err
)
}
if
count
!=
8
{
return
fmt
.
Errorf
(
"expected L1Block to have 8 storage slots, but got %d"
,
count
)
}
log
.
Debug
(
"validated L1Block storage slot count"
,
"expected"
,
8
)
return
nil
}
func
CheckWithdrawalsAfter
(
db
*
state
.
StateDB
,
data
crossdomain
.
MigrationData
,
l1CrossDomainMessenger
*
common
.
Address
,
l2ChainID
*
big
.
Int
)
error
{
wds
,
invalidMessages
,
err
:=
data
.
ToWithdrawals
()
if
err
!=
nil
{
return
err
}
// First, make a mapping between old withdrawal slots and new ones.
// This list can be a superset of what was actually migrated, since
// some witness data may references withdrawals that reverted.
oldToNewSlots
:=
make
(
map
[
common
.
Hash
]
common
.
Hash
)
wdsByOldSlot
:=
make
(
map
[
common
.
Hash
]
*
crossdomain
.
LegacyWithdrawal
)
invalidMessagesByOldSlot
:=
make
(
map
[
common
.
Hash
]
crossdomain
.
InvalidMessage
)
for
_
,
wd
:=
range
wds
{
migrated
,
err
:=
crossdomain
.
MigrateWithdrawal
(
wd
,
l1CrossDomainMessenger
,
l2ChainID
)
if
err
!=
nil
{
return
err
}
legacySlot
,
err
:=
wd
.
StorageSlot
()
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot compute legacy storage slot: %w"
,
err
)
}
migratedSlot
,
err
:=
migrated
.
StorageSlot
()
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot compute migrated storage slot: %w"
,
err
)
}
oldToNewSlots
[
legacySlot
]
=
migratedSlot
wdsByOldSlot
[
legacySlot
]
=
wd
}
for
_
,
im
:=
range
invalidMessages
{
invalidSlot
,
err
:=
im
.
StorageSlot
()
if
err
!=
nil
{
return
fmt
.
Errorf
(
"cannot compute legacy storage slot: %w"
,
err
)
}
invalidMessagesByOldSlot
[
invalidSlot
]
=
im
}
log
.
Info
(
"computed withdrawal storage slots"
,
"migrated"
,
len
(
oldToNewSlots
),
"invalid"
,
len
(
invalidMessagesByOldSlot
))
// Now, iterate over each legacy withdrawal and check if there is a corresponding
// migrated withdrawal.
var
innerErr
error
progress
:=
util
.
ProgressLogger
(
1000
,
"checking withdrawals"
)
err
=
db
.
ForEachStorage
(
predeploys
.
LegacyMessagePasserAddr
,
func
(
key
,
value
common
.
Hash
)
bool
{
progress
()
// The legacy message passer becomes a proxy during the migration,
// so we need to ignore the implementation/admin slots.
if
key
==
ImplementationSlot
||
key
==
AdminSlot
{
return
true
}
// All other values should be abiTrue, since the only other state
// in the message passer is the mapping of messages to boolean true.
if
value
!=
abiTrue
{
innerErr
=
fmt
.
Errorf
(
"non-true value found in legacy message passer. key: %s, value: %s"
,
key
,
value
)
return
false
}
// Make sure invalid slots don't get migrated.
_
,
isInvalidSlot
:=
invalidMessagesByOldSlot
[
key
]
if
isInvalidSlot
{
value
:=
db
.
GetState
(
predeploys
.
L2ToL1MessagePasserAddr
,
key
)
if
value
!=
abiFalse
{
innerErr
=
fmt
.
Errorf
(
"expected invalid slot not to be migrated, but got %s"
,
value
)
return
false
}
return
true
}
// Grab the migrated slot.
migratedSlot
:=
oldToNewSlots
[
key
]
if
migratedSlot
==
(
common
.
Hash
{})
{
innerErr
=
fmt
.
Errorf
(
"no migrated slot found for legacy slot %s"
,
key
)
return
false
}
// Look up the migrated slot in the DB.
migratedValue
:=
db
.
GetState
(
predeploys
.
L2ToL1MessagePasserAddr
,
migratedSlot
)
// If the sender is _not_ the L2XDM, the value should not be migrated.
wd
:=
wdsByOldSlot
[
key
]
if
wd
.
MessageSender
==
predeploys
.
L2CrossDomainMessengerAddr
{
// Make sure the value is abiTrue if this withdrawal should be migrated.
if
migratedValue
!=
abiTrue
{
innerErr
=
fmt
.
Errorf
(
"expected migrated value to be true, but got %s"
,
migratedValue
)
return
false
}
}
else
{
// Otherwise, ensure that withdrawals from senders other than the L2XDM are _not_ migrated.
if
migratedValue
!=
abiFalse
{
innerErr
=
fmt
.
Errorf
(
"a migration from a sender other than the L2XDM was migrated. sender: %s, migrated value: %s"
,
wd
.
MessageSender
,
migratedValue
)
return
false
}
}
return
true
})
if
err
!=
nil
{
return
fmt
.
Errorf
(
"error iterating storage slots: %w"
,
err
)
}
if
innerErr
!=
nil
{
return
fmt
.
Errorf
(
"error checking storage slots: %w"
,
innerErr
)
}
return
nil
}
func
eip1967Slots
(
address
common
.
Address
)
StorageCheckMap
{
codeAddr
,
err
:=
AddressToCodeNamespace
(
address
)
if
err
!=
nil
{
panic
(
err
)
}
return
StorageCheckMap
{
AdminSlot
:
predeploys
.
ProxyAdminAddr
.
Hash
(),
ImplementationSlot
:
codeAddr
.
Hash
(),
}
}
op-chain-ops/genesis/db_migration.go
deleted
100644 → 0
View file @
cedeb4ec
package
genesis
import
(
"bytes"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/ether"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)
var
(
abiTrue
=
common
.
Hash
{
31
:
0x01
}
abiFalse
=
common
.
Hash
{}
// BedrockTransitionBlockExtraData represents the extradata
// set in the very first bedrock block. This value must be
// less than 32 bytes long or it will create an invalid block.
BedrockTransitionBlockExtraData
=
[]
byte
(
"BEDROCK"
)
)
type
MigrationResult
struct
{
TransitionHeight
uint64
TransitionTimestamp
uint64
TransitionBlockHash
common
.
Hash
}
// MigrateDB will migrate an l2geth legacy Optimism database to a Bedrock database.
func
MigrateDB
(
ldb
ethdb
.
Database
,
config
*
DeployConfig
,
l1Block
*
types
.
Block
,
migrationData
*
crossdomain
.
MigrationData
,
commit
,
noCheck
bool
)
(
*
MigrationResult
,
error
)
{
// Grab the hash of the tip of the legacy chain.
hash
:=
rawdb
.
ReadHeadHeaderHash
(
ldb
)
log
.
Info
(
"Reading chain tip from database"
,
"hash"
,
hash
)
// Grab the header number.
num
:=
rawdb
.
ReadHeaderNumber
(
ldb
,
hash
)
if
num
==
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot find header number for %s"
,
hash
)
}
// Grab the full header.
header
:=
rawdb
.
ReadHeader
(
ldb
,
hash
,
*
num
)
log
.
Info
(
"Read header from database"
,
"number"
,
*
num
)
// Ensure that the extradata is valid.
if
size
:=
len
(
BedrockTransitionBlockExtraData
);
size
>
32
{
return
nil
,
fmt
.
Errorf
(
"transition block extradata too long: %d"
,
size
)
}
// We write special extra data into the Bedrock transition block to indicate that the migration
// has already happened. If we detect this extra data, we can skip the migration.
if
bytes
.
Equal
(
header
.
Extra
,
BedrockTransitionBlockExtraData
)
{
log
.
Info
(
"Detected migration already happened"
,
"root"
,
header
.
Root
,
"blockhash"
,
header
.
Hash
())
return
&
MigrationResult
{
TransitionHeight
:
*
num
,
TransitionTimestamp
:
header
.
Time
,
TransitionBlockHash
:
hash
,
},
nil
}
// Ensure that the timestamp for the Bedrock transition block is greater than the timestamp of
// the last legacy block.
if
uint64
(
config
.
L2OutputOracleStartingTimestamp
)
<=
header
.
Time
{
return
nil
,
fmt
.
Errorf
(
"output oracle starting timestamp (%d) is less than the header timestamp (%d)"
,
config
.
L2OutputOracleStartingTimestamp
,
header
.
Time
,
)
}
// Ensure that the timestamp for the Bedrock transition block is greater than 0, not implicitly
// guaranteed by the above check because the above converted the timestamp to a uint64.
if
config
.
L2OutputOracleStartingTimestamp
<=
0
{
return
nil
,
fmt
.
Errorf
(
"output oracle starting timestamp (%d) cannot be <= 0"
,
config
.
L2OutputOracleStartingTimestamp
,
)
}
dbFactory
:=
func
()
(
*
state
.
StateDB
,
error
)
{
// Set up the backing store.
underlyingDB
:=
state
.
NewDatabaseWithConfig
(
ldb
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
})
// Open up the state database.
db
,
err
:=
state
.
New
(
header
.
Root
,
underlyingDB
,
nil
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot open StateDB: %w"
,
err
)
}
return
db
,
nil
}
db
,
err
:=
dbFactory
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot create StateDB: %w"
,
err
)
}
// Before we do anything else, we need to ensure that all of the input configuration is correct
// and nothing is missing. We'll first verify the contract configuration, then we'll verify the
// witness data for the migration. We operate under the assumption that the witness data is
// untrusted and must be verified explicitly before we can use it.
// Generate and verify the configuration for storage variables to be set on L2.
storage
,
err
:=
NewL2StorageConfig
(
config
,
l1Block
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot create storage config: %w"
,
err
)
}
// Generate and verify the configuration for immutable variables to be set on L2.
immutable
,
err
:=
NewL2ImmutableConfig
(
config
,
l1Block
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot create immutable config: %w"
,
err
)
}
// Convert all input messages into legacy messages. Note that this list is not yet filtered and
// may be missing some messages or have some extra messages.
unfilteredWithdrawals
,
invalidMessages
,
err
:=
migrationData
.
ToWithdrawals
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot serialize withdrawals: %w"
,
err
)
}
log
.
Info
(
"Read withdrawals from witness data"
,
"unfiltered"
,
len
(
unfilteredWithdrawals
),
"invalid"
,
len
(
invalidMessages
))
// We now need to check that we have all of the withdrawals that we expect to have. An error
// will be thrown if there are any missing messages, and any extra messages will be removed.
var
filteredWithdrawals
crossdomain
.
SafeFilteredWithdrawals
if
!
noCheck
{
log
.
Info
(
"Checking withdrawals..."
)
filteredWithdrawals
,
err
=
crossdomain
.
PreCheckWithdrawals
(
db
,
unfilteredWithdrawals
,
invalidMessages
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"withdrawals mismatch: %w"
,
err
)
}
}
else
{
log
.
Info
(
"Skipping checking withdrawals"
)
filteredWithdrawals
=
crossdomain
.
SafeFilteredWithdrawals
(
unfilteredWithdrawals
)
}
// At this point we've fully verified the witness data for the migration, so we can begin the
// actual migration process. This involves modifying parts of the legacy database and inserting
// a transition block.
// We need to wipe the storage of every predeployed contract EXCEPT for the GovernanceToken,
// WETH9, the DeployerWhitelist, the LegacyMessagePasser, and LegacyERC20ETH. We have verified
// that none of the legacy storage (other than the aforementioned contracts) is accessible and
// therefore can be safely removed from the database. Storage must be wiped before anything
// else or the ERC-1967 proxy storage slots will be removed.
if
err
:=
WipePredeployStorage
(
db
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot wipe storage: %w"
,
err
)
}
// Next order of business is to convert all predeployed smart contracts into proxies so they
// can be easily upgraded later on. In the legacy system, all upgrades to predeployed contracts
// required hard forks which was a huge pain. Note that we do NOT put the GovernanceToken or
// WETH9 contracts behind proxies because we do not want to make these easily upgradable.
log
.
Info
(
"Converting predeployed contracts to proxies"
)
if
err
:=
SetL2Proxies
(
db
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot set L2Proxies: %w"
,
err
)
}
// Here we update the storage of each predeploy with the new storage variables that we want to
// set on L2 and update the implementations for all predeployed contracts that are behind
// proxies (NOT the GovernanceToken or WETH9).
log
.
Info
(
"Updating implementations for predeployed contracts"
)
if
err
:=
SetImplementations
(
db
,
storage
,
immutable
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot set implementations: %w"
,
err
)
}
// We need to update the code for LegacyERC20ETH. This is NOT a standard predeploy because it's
// deployed at the 0xdeaddeaddead... address and therefore won't be updated by the previous
// function call to SetImplementations.
log
.
Info
(
"Updating code for LegacyERC20ETH"
)
if
err
:=
SetLegacyETH
(
db
,
storage
,
immutable
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot set legacy ETH: %w"
,
err
)
}
// Now we migrate legacy withdrawals from the LegacyMessagePasser contract to their new format
// in the Bedrock L2ToL1MessagePasser contract. Note that we do NOT delete the withdrawals from
// the LegacyMessagePasser contract. Here we operate on the list of withdrawals that we
// previously filtered and verified.
log
.
Info
(
"Starting to migrate withdrawals"
,
"no-check"
,
noCheck
)
l2ChainID
:=
new
(
big
.
Int
)
.
SetUint64
(
config
.
L2ChainID
)
err
=
crossdomain
.
MigrateWithdrawals
(
filteredWithdrawals
,
db
,
&
config
.
L1CrossDomainMessengerProxy
,
noCheck
,
l2ChainID
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot migrate withdrawals: %w"
,
err
)
}
// Finally we migrate the balances held inside the LegacyERC20ETH contract into the state trie.
// We also delete the balances from the LegacyERC20ETH contract. Unlike the steps above, this step
// combines the check and mutation steps into one in order to reduce migration time.
log
.
Info
(
"Starting to migrate ERC20 ETH"
)
err
=
ether
.
MigrateBalances
(
db
,
dbFactory
,
migrationData
.
Addresses
(),
migrationData
.
OvmAllowances
,
int
(
config
.
L1ChainID
),
noCheck
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to migrate OVM_ETH: %w"
,
err
)
}
// We're done messing around with the database, so we can now commit the changes to the DB.
// Note that this doesn't actually write the changes to disk.
log
.
Info
(
"Committing state DB"
)
newRoot
,
err
:=
db
.
Commit
(
true
)
if
err
!=
nil
{
return
nil
,
err
}
// Create the header for the Bedrock transition block.
bedrockHeader
:=
&
types
.
Header
{
ParentHash
:
header
.
Hash
(),
UncleHash
:
types
.
EmptyUncleHash
,
Coinbase
:
predeploys
.
SequencerFeeVaultAddr
,
Root
:
newRoot
,
TxHash
:
types
.
EmptyRootHash
,
ReceiptHash
:
types
.
EmptyRootHash
,
Bloom
:
types
.
Bloom
{},
Difficulty
:
common
.
Big0
,
Number
:
new
(
big
.
Int
)
.
Add
(
header
.
Number
,
common
.
Big1
),
GasLimit
:
(
uint64
)(
config
.
L2GenesisBlockGasLimit
),
GasUsed
:
0
,
Time
:
uint64
(
config
.
L2OutputOracleStartingTimestamp
),
Extra
:
BedrockTransitionBlockExtraData
,
MixDigest
:
common
.
Hash
{},
Nonce
:
types
.
BlockNonce
{},
BaseFee
:
big
.
NewInt
(
params
.
InitialBaseFee
),
}
// Create the Bedrock transition block from the header. Note that there are no transactions,
// uncle blocks, or receipts in the Bedrock transition block.
bedrockBlock
:=
types
.
NewBlock
(
bedrockHeader
,
nil
,
nil
,
nil
,
trie
.
NewStackTrie
(
nil
))
// We did it!
log
.
Info
(
"Built Bedrock transition"
,
"hash"
,
bedrockBlock
.
Hash
(),
"root"
,
bedrockBlock
.
Root
(),
"number"
,
bedrockBlock
.
NumberU64
(),
"gas-used"
,
bedrockBlock
.
GasUsed
(),
"gas-limit"
,
bedrockBlock
.
GasLimit
(),
)
// Create the result of the migration.
res
:=
&
MigrationResult
{
TransitionHeight
:
bedrockBlock
.
NumberU64
(),
TransitionTimestamp
:
bedrockBlock
.
Time
(),
TransitionBlockHash
:
bedrockBlock
.
Hash
(),
}
// If we're not actually writing this to disk, then we're done.
if
!
commit
{
log
.
Info
(
"Dry run complete"
)
return
res
,
nil
}
// Otherwise we need to write the changes to disk. First we commit the state changes.
log
.
Info
(
"Committing trie DB"
)
if
err
:=
db
.
Database
()
.
TrieDB
()
.
Commit
(
newRoot
,
true
);
err
!=
nil
{
return
nil
,
err
}
// Next we write the Bedrock transition block to the database.
rawdb
.
WriteTd
(
ldb
,
bedrockBlock
.
Hash
(),
bedrockBlock
.
NumberU64
(),
bedrockBlock
.
Difficulty
())
rawdb
.
WriteBlock
(
ldb
,
bedrockBlock
)
rawdb
.
WriteReceipts
(
ldb
,
bedrockBlock
.
Hash
(),
bedrockBlock
.
NumberU64
(),
nil
)
rawdb
.
WriteCanonicalHash
(
ldb
,
bedrockBlock
.
Hash
(),
bedrockBlock
.
NumberU64
())
rawdb
.
WriteHeadBlockHash
(
ldb
,
bedrockBlock
.
Hash
())
rawdb
.
WriteHeadFastBlockHash
(
ldb
,
bedrockBlock
.
Hash
())
rawdb
.
WriteHeadHeaderHash
(
ldb
,
bedrockBlock
.
Hash
())
// Make the first Bedrock block a finalized block.
rawdb
.
WriteFinalizedBlockHash
(
ldb
,
bedrockBlock
.
Hash
())
// We need to update the chain config to set the correct hardforks.
genesisHash
:=
rawdb
.
ReadCanonicalHash
(
ldb
,
0
)
cfg
:=
rawdb
.
ReadChainConfig
(
ldb
,
genesisHash
)
if
cfg
==
nil
{
log
.
Crit
(
"chain config not found"
)
}
// Set the standard options.
cfg
.
LondonBlock
=
bedrockBlock
.
Number
()
cfg
.
ArrowGlacierBlock
=
bedrockBlock
.
Number
()
cfg
.
GrayGlacierBlock
=
bedrockBlock
.
Number
()
cfg
.
MergeNetsplitBlock
=
bedrockBlock
.
Number
()
cfg
.
TerminalTotalDifficulty
=
big
.
NewInt
(
0
)
cfg
.
TerminalTotalDifficultyPassed
=
true
// Set the Optimism options.
cfg
.
BedrockBlock
=
bedrockBlock
.
Number
()
// Enable Regolith from the start of Bedrock
cfg
.
RegolithTime
=
new
(
uint64
)
cfg
.
Optimism
=
&
params
.
OptimismConfig
{
EIP1559Denominator
:
config
.
EIP1559Denominator
,
EIP1559Elasticity
:
config
.
EIP1559Elasticity
,
}
// Write the chain config to disk.
rawdb
.
WriteChainConfig
(
ldb
,
genesisHash
,
cfg
)
// Yay!
log
.
Info
(
"wrote chain config"
,
"1559-denominator"
,
config
.
EIP1559Denominator
,
"1559-elasticity"
,
config
.
EIP1559Elasticity
,
)
// We're done!
log
.
Info
(
"wrote Bedrock transition block"
,
"height"
,
bedrockHeader
.
Number
,
"root"
,
bedrockHeader
.
Root
.
String
(),
"hash"
,
bedrockHeader
.
Hash
()
.
String
(),
"timestamp"
,
bedrockHeader
.
Time
,
)
// Return the result and have a nice day.
return
res
,
nil
}
op-chain-ops/genesis/genesis.go
View file @
dfee56f5
...
@@ -18,6 +18,9 @@ import (
...
@@ -18,6 +18,9 @@ import (
// defaultL2GasLimit represents the default gas limit for an L2 block.
// defaultL2GasLimit represents the default gas limit for an L2 block.
const
defaultL2GasLimit
=
30
_000_000
const
defaultL2GasLimit
=
30
_000_000
// BedrockTransitionBlockExtraData represents the default extra data for the bedrock transition block.
var
BedrockTransitionBlockExtraData
=
[]
byte
(
"BEDROCK"
)
// NewL2Genesis will create a new L2 genesis
// NewL2Genesis will create a new L2 genesis
func
NewL2Genesis
(
config
*
DeployConfig
,
block
*
types
.
Block
)
(
*
core
.
Genesis
,
error
)
{
func
NewL2Genesis
(
config
*
DeployConfig
,
block
*
types
.
Block
)
(
*
core
.
Genesis
,
error
)
{
if
config
.
L2ChainID
==
0
{
if
config
.
L2ChainID
==
0
{
...
...
op-chain-ops/genesis/migration_action/action.go
deleted
100644 → 0
View file @
cedeb4ec
package
migration_action
import
(
"context"
"math/big"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethclient"
)
type
Config
struct
{
DeployConfig
*
genesis
.
DeployConfig
OVMAddressesPath
string
EVMAddressesPath
string
OVMAllowancesPath
string
OVMMessagesPath
string
EVMMessagesPath
string
Network
string
HardhatDeployments
[]
string
L1URL
string
StartingL1BlockNumber
uint64
L2DBPath
string
DryRun
bool
NoCheck
bool
}
func
Migrate
(
cfg
*
Config
)
(
*
genesis
.
MigrationResult
,
error
)
{
deployConfig
:=
cfg
.
DeployConfig
ovmAddresses
,
err
:=
crossdomain
.
NewAddresses
(
cfg
.
OVMAddressesPath
)
if
err
!=
nil
{
return
nil
,
err
}
evmAddresess
,
err
:=
crossdomain
.
NewAddresses
(
cfg
.
EVMAddressesPath
)
if
err
!=
nil
{
return
nil
,
err
}
ovmAllowances
,
err
:=
crossdomain
.
NewAllowances
(
cfg
.
OVMAllowancesPath
)
if
err
!=
nil
{
return
nil
,
err
}
ovmMessages
,
err
:=
crossdomain
.
NewSentMessageFromJSON
(
cfg
.
OVMMessagesPath
)
if
err
!=
nil
{
return
nil
,
err
}
evmMessages
,
err
:=
crossdomain
.
NewSentMessageFromJSON
(
cfg
.
EVMMessagesPath
)
if
err
!=
nil
{
return
nil
,
err
}
migrationData
:=
crossdomain
.
MigrationData
{
OvmAddresses
:
ovmAddresses
,
EvmAddresses
:
evmAddresess
,
OvmAllowances
:
ovmAllowances
,
OvmMessages
:
ovmMessages
,
EvmMessages
:
evmMessages
,
}
l1Client
,
err
:=
ethclient
.
Dial
(
cfg
.
L1URL
)
if
err
!=
nil
{
return
nil
,
err
}
var
blockNumber
*
big
.
Int
bnum
:=
cfg
.
StartingL1BlockNumber
if
bnum
!=
0
{
blockNumber
=
new
(
big
.
Int
)
.
SetUint64
(
bnum
)
}
block
,
err
:=
l1Client
.
BlockByNumber
(
context
.
Background
(),
blockNumber
)
if
err
!=
nil
{
return
nil
,
err
}
chaindataPath
:=
filepath
.
Join
(
cfg
.
L2DBPath
,
"geth"
,
"chaindata"
)
ancientPath
:=
filepath
.
Join
(
chaindataPath
,
"ancient"
)
ldb
,
err
:=
rawdb
.
Open
(
rawdb
.
OpenOptions
{
Type
:
"leveldb"
,
Directory
:
chaindataPath
,
Cache
:
4096
,
Handles
:
120
,
AncientsDirectory
:
ancientPath
,
Namespace
:
""
,
ReadOnly
:
false
,
})
if
err
!=
nil
{
return
nil
,
err
}
defer
ldb
.
Close
()
return
genesis
.
MigrateDB
(
ldb
,
deployConfig
,
block
,
&
migrationData
,
!
cfg
.
DryRun
,
cfg
.
NoCheck
)
}
op-chain-ops/genesis/setters.go
View file @
dfee56f5
...
@@ -26,33 +26,6 @@ var (
...
@@ -26,33 +26,6 @@ var (
predeploys
.
GovernanceTokenAddr
:
true
,
predeploys
.
GovernanceTokenAddr
:
true
,
predeploys
.
WETH9Addr
:
true
,
predeploys
.
WETH9Addr
:
true
,
}
}
// UntouchableCodeHashes represent the bytecode hashes of contracts
// that should not be touched by the migration process.
UntouchableCodeHashes
=
map
[
common
.
Address
]
ChainHashMap
{
predeploys
.
GovernanceTokenAddr
:
{
1
:
common
.
HexToHash
(
"0x8551d935f4e67ad3c98609f0d9f0f234740c4c4599f82674633b55204393e07f"
),
5
:
common
.
HexToHash
(
"0xc4a213cf5f06418533e5168d8d82f7ccbcc97f27ab90197c2c051af6a4941cf9"
),
},
predeploys
.
WETH9Addr
:
{
1
:
common
.
HexToHash
(
"0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173"
),
5
:
common
.
HexToHash
(
"0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173"
),
},
}
// FrozenStoragePredeploys represents the set of predeploys that
// will not have their storage wiped during the migration process.
// It is very explicitly set in its own mapping to ensure that
// changes elsewhere in the codebase do no alter the predeploys
// that do not have their storage wiped. It is safe for all other
// predeploys to have their storage wiped.
FrozenStoragePredeploys
=
map
[
common
.
Address
]
bool
{
predeploys
.
GovernanceTokenAddr
:
true
,
predeploys
.
WETH9Addr
:
true
,
predeploys
.
LegacyMessagePasserAddr
:
true
,
predeploys
.
LegacyERC20ETHAddr
:
true
,
predeploys
.
DeployerWhitelistAddr
:
true
,
}
)
)
// FundDevAccounts will fund each of the development accounts.
// FundDevAccounts will fund each of the development accounts.
...
@@ -79,32 +52,6 @@ func SetL1Proxies(db vm.StateDB, proxyAdminAddr common.Address) error {
...
@@ -79,32 +52,6 @@ func SetL1Proxies(db vm.StateDB, proxyAdminAddr common.Address) error {
return
setProxies
(
db
,
proxyAdminAddr
,
bigL1PredeployNamespace
,
2048
)
return
setProxies
(
db
,
proxyAdminAddr
,
bigL1PredeployNamespace
,
2048
)
}
}
// WipePredeployStorage will wipe the storage of all L2 predeploys expect
// for predeploys that must not have their storage altered.
func
WipePredeployStorage
(
db
vm
.
StateDB
)
error
{
for
name
,
addr
:=
range
predeploys
.
Predeploys
{
if
addr
==
nil
{
return
fmt
.
Errorf
(
"nil address in predeploys mapping for %s"
,
name
)
}
if
FrozenStoragePredeploys
[
*
addr
]
{
log
.
Trace
(
"skipping wiping of storage"
,
"name"
,
name
,
"address"
,
*
addr
)
continue
}
log
.
Info
(
"wiping storage"
,
"name"
,
name
,
"address"
,
*
addr
)
// We need to make sure that we preserve nonces.
oldNonce
:=
db
.
GetNonce
(
*
addr
)
db
.
CreateAccount
(
*
addr
)
if
oldNonce
>
0
{
db
.
SetNonce
(
*
addr
,
oldNonce
)
}
}
return
nil
}
func
setProxies
(
db
vm
.
StateDB
,
proxyAdminAddr
common
.
Address
,
namespace
*
big
.
Int
,
count
uint64
)
error
{
func
setProxies
(
db
vm
.
StateDB
,
proxyAdminAddr
common
.
Address
,
namespace
*
big
.
Int
,
count
uint64
)
error
{
depBytecode
,
err
:=
bindings
.
GetDeployedBytecode
(
"Proxy"
)
depBytecode
,
err
:=
bindings
.
GetDeployedBytecode
(
"Proxy"
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
op-chain-ops/genesis/setters_test.go
deleted
100644 → 0
View file @
cedeb4ec
package
genesis
import
(
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/require"
)
func
TestWipePredeployStorage
(
t
*
testing
.
T
)
{
rawDB
:=
rawdb
.
NewMemoryDatabase
()
rawStateDB
:=
state
.
NewDatabaseWithConfig
(
rawDB
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
})
stateDB
,
err
:=
state
.
New
(
common
.
Hash
{},
rawStateDB
,
nil
)
require
.
NoError
(
t
,
err
)
storeVal
:=
common
.
Hash
{
31
:
0xff
}
for
_
,
addr
:=
range
predeploys
.
Predeploys
{
a
:=
*
addr
stateDB
.
SetState
(
a
,
storeVal
,
storeVal
)
stateDB
.
SetBalance
(
a
,
big
.
NewInt
(
99
))
stateDB
.
SetNonce
(
a
,
99
)
}
root
,
err
:=
stateDB
.
Commit
(
false
)
require
.
NoError
(
t
,
err
)
err
=
stateDB
.
Database
()
.
TrieDB
()
.
Commit
(
root
,
true
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
WipePredeployStorage
(
stateDB
))
for
_
,
addr
:=
range
predeploys
.
Predeploys
{
a
:=
*
addr
if
FrozenStoragePredeploys
[
a
]
{
require
.
Equal
(
t
,
storeVal
,
stateDB
.
GetState
(
a
,
storeVal
))
}
else
{
require
.
Equal
(
t
,
common
.
Hash
{},
stateDB
.
GetState
(
a
,
storeVal
))
}
require
.
Equal
(
t
,
big
.
NewInt
(
99
),
stateDB
.
GetBalance
(
a
))
require
.
Equal
(
t
,
uint64
(
99
),
stateDB
.
GetNonce
(
a
))
}
}
op-chain-ops/genesis/test_util.go
deleted
100644 → 0
View file @
cedeb4ec
package
genesis
import
(
"archive/tar"
"compress/gzip"
"io"
"os"
"path/filepath"
)
func
Untar
(
tarball
,
target
string
)
error
{
f
,
err
:=
os
.
Open
(
tarball
)
if
err
!=
nil
{
return
err
}
defer
f
.
Close
()
r
,
err
:=
gzip
.
NewReader
(
f
)
if
err
!=
nil
{
return
err
}
tarReader
:=
tar
.
NewReader
(
r
)
for
{
header
,
err
:=
tarReader
.
Next
()
if
err
==
io
.
EOF
{
break
}
else
if
err
!=
nil
{
return
err
}
path
:=
filepath
.
Join
(
target
,
header
.
Name
)
info
:=
header
.
FileInfo
()
if
info
.
IsDir
()
{
if
err
=
os
.
MkdirAll
(
path
,
info
.
Mode
());
err
!=
nil
{
return
err
}
continue
}
file
,
err
:=
os
.
OpenFile
(
path
,
os
.
O_CREATE
|
os
.
O_TRUNC
|
os
.
O_WRONLY
,
info
.
Mode
())
if
err
!=
nil
{
return
err
}
defer
file
.
Close
()
_
,
err
=
io
.
Copy
(
file
,
tarReader
)
if
err
!=
nil
{
return
err
}
}
return
nil
}
op-chain-ops/util/state_iterator.go
deleted
100644 → 0
View file @
cedeb4ec
package
util
import
(
"fmt"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
var
(
// maxSlot is the maximum possible storage slot.
maxSlot
=
common
.
HexToHash
(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
)
)
type
DBFactory
func
()
(
*
state
.
StateDB
,
error
)
type
StateCallback
func
(
db
*
state
.
StateDB
,
key
,
value
common
.
Hash
)
error
func
IterateState
(
dbFactory
DBFactory
,
address
common
.
Address
,
cb
StateCallback
,
workers
int
)
error
{
if
workers
<=
0
{
panic
(
"workers must be greater than 0"
)
}
// WaitGroup to wait for all workers to finish.
var
wg
sync
.
WaitGroup
// Channel to receive errors from each iteration job.
errCh
:=
make
(
chan
error
,
workers
)
// Channel to cancel all iteration jobs.
cancelCh
:=
make
(
chan
struct
{})
worker
:=
func
(
start
,
end
common
.
Hash
)
{
// Decrement the WaitGroup when the function returns.
defer
wg
.
Done
()
db
,
err
:=
dbFactory
()
if
err
!=
nil
{
// Should never happen, so explode if it does.
log
.
Crit
(
"cannot create state db"
,
"err"
,
err
)
}
st
,
err
:=
db
.
StorageTrie
(
address
)
if
err
!=
nil
{
// Should never happen, so explode if it does.
log
.
Crit
(
"cannot get storage trie"
,
"address"
,
address
,
"err"
,
err
)
}
// st can be nil if the account doesn't exist.
if
st
==
nil
{
errCh
<-
fmt
.
Errorf
(
"account does not exist: %s"
,
address
.
Hex
())
return
}
it
:=
trie
.
NewIterator
(
st
.
NodeIterator
(
start
.
Bytes
()))
// Below code is largely based on db.ForEachStorage. We can't use that
// because it doesn't allow us to specify a start and end key.
for
it
.
Next
()
{
select
{
case
<-
cancelCh
:
// If one of the workers encounters an error, cancel all of them.
return
default
:
break
}
// Use the raw (i.e., secure hashed) key to check if we've reached
// the end of the partition. Use > rather than >= here to account for
// the fact that the values returned by PartitionKeys are inclusive.
// Duplicate addresses that may be returned by this iteration are
// filtered out in the collector.
if
new
(
big
.
Int
)
.
SetBytes
(
it
.
Key
)
.
Cmp
(
end
.
Big
())
>
0
{
return
}
// Skip if the value is empty.
rawValue
:=
it
.
Value
if
len
(
rawValue
)
==
0
{
continue
}
// Get the preimage.
rawKey
:=
st
.
GetKey
(
it
.
Key
)
if
rawKey
==
nil
{
// Should never happen, so explode if it does.
log
.
Crit
(
"cannot get preimage for storage key"
,
"key"
,
it
.
Key
)
}
key
:=
common
.
BytesToHash
(
rawKey
)
// Parse the raw value.
_
,
content
,
_
,
err
:=
rlp
.
Split
(
rawValue
)
if
err
!=
nil
{
// Should never happen, so explode if it does.
log
.
Crit
(
"mal-formed data in state: %v"
,
err
)
}
value
:=
common
.
BytesToHash
(
content
)
// Call the callback with the DB, key, and value. Errors get
// bubbled up to the errCh.
if
err
:=
cb
(
db
,
key
,
value
);
err
!=
nil
{
errCh
<-
err
return
}
}
}
for
i
:=
0
;
i
<
workers
;
i
++
{
wg
.
Add
(
1
)
// Partition the keyspace per worker.
start
,
end
:=
PartitionKeyspace
(
i
,
workers
)
// Kick off our worker.
go
worker
(
start
,
end
)
}
wg
.
Wait
()
for
len
(
errCh
)
>
0
{
err
:=
<-
errCh
if
err
!=
nil
{
return
err
}
}
return
nil
}
// PartitionKeyspace divides the key space into partitions by dividing the maximum keyspace
// by count then multiplying by i. This will leave some slots left over, which we handle below. It
// returns the start and end keys for the partition as a common.Hash. Note that the returned range
// of keys is inclusive, i.e., [start, end] NOT [start, end).
func
PartitionKeyspace
(
i
int
,
count
int
)
(
common
.
Hash
,
common
.
Hash
)
{
if
i
<
0
||
count
<
0
{
panic
(
"i and count must be greater than 0"
)
}
if
i
>
count
-
1
{
panic
(
"i must be less than count - 1"
)
}
// Divide the key space into partitions by dividing the key space by the number
// of jobs. This will leave some slots left over, which we handle below.
partSize
:=
new
(
big
.
Int
)
.
Div
(
maxSlot
.
Big
(),
big
.
NewInt
(
int64
(
count
)))
start
:=
common
.
BigToHash
(
new
(
big
.
Int
)
.
Mul
(
big
.
NewInt
(
int64
(
i
)),
partSize
))
var
end
common
.
Hash
if
i
<
count
-
1
{
// If this is not the last partition, use the next partition's start key as the end.
end
=
common
.
BigToHash
(
new
(
big
.
Int
)
.
Mul
(
big
.
NewInt
(
int64
(
i
+
1
)),
partSize
))
}
else
{
// If this is the last partition, use the max slot as the end.
end
=
maxSlot
}
return
start
,
end
}
op-chain-ops/util/state_iterator_test.go
deleted
100644 → 0
View file @
cedeb4ec
package
util
import
(
crand
"crypto/rand"
"fmt"
"math/rand"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/require"
)
var
testAddr
=
common
.
Address
{
0
:
0xff
}
func
TestStateIteratorWorkers
(
t
*
testing
.
T
)
{
_
,
factory
,
_
:=
setupRandTest
(
t
)
for
i
:=
-
1
;
i
<=
0
;
i
++
{
require
.
Panics
(
t
,
func
()
{
_
=
IterateState
(
factory
,
testAddr
,
func
(
db
*
state
.
StateDB
,
key
,
value
common
.
Hash
)
error
{
return
nil
},
i
)
})
}
}
func
TestStateIteratorNonexistentAccount
(
t
*
testing
.
T
)
{
_
,
factory
,
_
:=
setupRandTest
(
t
)
require
.
ErrorContains
(
t
,
IterateState
(
factory
,
common
.
Address
{},
func
(
db
*
state
.
StateDB
,
key
,
value
common
.
Hash
)
error
{
return
nil
},
1
),
"account does not exist"
)
}
func
TestStateIteratorRandomOK
(
t
*
testing
.
T
)
{
for
i
:=
0
;
i
<
100
;
i
++
{
hashes
,
factory
,
workerCount
:=
setupRandTest
(
t
)
seenHashes
:=
make
(
map
[
common
.
Hash
]
bool
)
hashCh
:=
make
(
chan
common
.
Hash
)
doneCh
:=
make
(
chan
struct
{})
go
func
()
{
defer
close
(
doneCh
)
for
hash
:=
range
hashCh
{
seenHashes
[
hash
]
=
true
}
}()
require
.
NoError
(
t
,
IterateState
(
factory
,
testAddr
,
func
(
db
*
state
.
StateDB
,
key
,
value
common
.
Hash
)
error
{
hashCh
<-
key
return
nil
},
workerCount
))
close
(
hashCh
)
<-
doneCh
// Perform a less or equal check here in case of duplicates. The map check below will assert
// that all of the hashes are accounted for.
require
.
LessOrEqual
(
t
,
len
(
seenHashes
),
len
(
hashes
))
// Every hash we put into state should have been iterated over.
for
_
,
hash
:=
range
hashes
{
require
.
Contains
(
t
,
seenHashes
,
hash
)
}
}
}
func
TestStateIteratorRandomError
(
t
*
testing
.
T
)
{
for
i
:=
0
;
i
<
100
;
i
++
{
hashes
,
factory
,
workerCount
:=
setupRandTest
(
t
)
failHash
:=
hashes
[
rand
.
Intn
(
len
(
hashes
))]
require
.
ErrorContains
(
t
,
IterateState
(
factory
,
testAddr
,
func
(
db
*
state
.
StateDB
,
key
,
value
common
.
Hash
)
error
{
if
key
==
failHash
{
return
fmt
.
Errorf
(
"test error"
)
}
return
nil
},
workerCount
),
"test error"
)
}
}
func
TestPartitionKeyspace
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
i
int
count
int
expected
[
2
]
common
.
Hash
}{
{
i
:
0
,
count
:
1
,
expected
:
[
2
]
common
.
Hash
{
common
.
HexToHash
(
"0x00"
),
common
.
HexToHash
(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
),
},
},
{
i
:
0
,
count
:
2
,
expected
:
[
2
]
common
.
Hash
{
common
.
HexToHash
(
"0x00"
),
common
.
HexToHash
(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
),
},
},
{
i
:
1
,
count
:
2
,
expected
:
[
2
]
common
.
Hash
{
common
.
HexToHash
(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
),
common
.
HexToHash
(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
),
},
},
{
i
:
0
,
count
:
3
,
expected
:
[
2
]
common
.
Hash
{
common
.
HexToHash
(
"0x00"
),
common
.
HexToHash
(
"0x5555555555555555555555555555555555555555555555555555555555555555"
),
},
},
{
i
:
1
,
count
:
3
,
expected
:
[
2
]
common
.
Hash
{
common
.
HexToHash
(
"0x5555555555555555555555555555555555555555555555555555555555555555"
),
common
.
HexToHash
(
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
),
},
},
{
i
:
2
,
count
:
3
,
expected
:
[
2
]
common
.
Hash
{
common
.
HexToHash
(
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
),
common
.
HexToHash
(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
),
},
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
fmt
.
Sprintf
(
"i %d, count %d"
,
tt
.
i
,
tt
.
count
),
func
(
t
*
testing
.
T
)
{
start
,
end
:=
PartitionKeyspace
(
tt
.
i
,
tt
.
count
)
require
.
Equal
(
t
,
tt
.
expected
[
0
],
start
)
require
.
Equal
(
t
,
tt
.
expected
[
1
],
end
)
})
}
t
.
Run
(
"panics on invalid i or count"
,
func
(
t
*
testing
.
T
)
{
require
.
Panics
(
t
,
func
()
{
PartitionKeyspace
(
1
,
1
)
})
require
.
Panics
(
t
,
func
()
{
PartitionKeyspace
(
-
1
,
1
)
})
require
.
Panics
(
t
,
func
()
{
PartitionKeyspace
(
0
,
-
1
)
})
require
.
Panics
(
t
,
func
()
{
PartitionKeyspace
(
-
1
,
-
1
)
})
})
}
func
setupRandTest
(
t
*
testing
.
T
)
([]
common
.
Hash
,
DBFactory
,
int
)
{
memDB
:=
rawdb
.
NewMemoryDatabase
()
db
,
err
:=
state
.
New
(
common
.
Hash
{},
state
.
NewDatabaseWithConfig
(
memDB
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
}),
nil
)
require
.
NoError
(
t
,
err
)
hashCount
:=
rand
.
Intn
(
100
)
if
hashCount
==
0
{
hashCount
=
1
}
hashes
:=
make
([]
common
.
Hash
,
hashCount
)
db
.
CreateAccount
(
testAddr
)
for
j
:=
0
;
j
<
hashCount
;
j
++
{
hashes
[
j
]
=
randHash
(
t
)
db
.
SetState
(
testAddr
,
hashes
[
j
],
hashes
[
j
])
}
root
,
err
:=
db
.
Commit
(
false
)
require
.
NoError
(
t
,
err
)
err
=
db
.
Database
()
.
TrieDB
()
.
Commit
(
root
,
true
)
require
.
NoError
(
t
,
err
)
factory
:=
func
()
(
*
state
.
StateDB
,
error
)
{
return
state
.
New
(
root
,
state
.
NewDatabaseWithConfig
(
memDB
,
&
trie
.
Config
{
Preimages
:
true
,
Cache
:
1024
,
}),
nil
)
}
workerCount
:=
rand
.
Intn
(
64
)
if
workerCount
==
0
{
workerCount
=
1
}
return
hashes
,
factory
,
workerCount
}
func
randHash
(
t
*
testing
.
T
)
common
.
Hash
{
var
h
common
.
Hash
_
,
err
:=
crand
.
Read
(
h
[
:
])
require
.
NoError
(
t
,
err
)
return
h
}
op-chain-ops/util/util.go
deleted
100644 → 0
View file @
cedeb4ec
package
util
import
(
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
func
ProgressLogger
(
n
int
,
msg
string
)
func
(
...
any
)
{
var
i
int
return
func
(
args
...
any
)
{
i
++
if
i
%
n
!=
0
{
return
}
log
.
Info
(
msg
,
append
([]
any
{
"count"
,
i
},
args
...
)
...
)
}
}
// clients represents a set of initialized RPC clients
type
Clients
struct
{
L1Client
*
ethclient
.
Client
L2Client
*
ethclient
.
Client
L1RpcClient
*
rpc
.
Client
L2RpcClient
*
rpc
.
Client
L1GethClient
*
gethclient
.
Client
L2GethClient
*
gethclient
.
Client
}
// NewClients will create new RPC clients from a CLI context
func
NewClients
(
ctx
*
cli
.
Context
)
(
*
Clients
,
error
)
{
l1RpcURL
:=
ctx
.
String
(
"l1-rpc-url"
)
l1Client
,
err
:=
ethclient
.
Dial
(
l1RpcURL
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot dial L1: %w"
,
err
)
}
l1ChainID
,
err
:=
l1Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot fetch L1 chainid: %w"
,
err
)
}
l2RpcURL
:=
ctx
.
String
(
"l2-rpc-url"
)
l2Client
,
err
:=
ethclient
.
Dial
(
l2RpcURL
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot dial L2: %w"
,
err
)
}
l2ChainID
,
err
:=
l2Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"cannot fetch L2 chainid: %w"
,
err
)
}
l1RpcClient
,
err
:=
rpc
.
DialContext
(
context
.
Background
(),
l1RpcURL
)
if
err
!=
nil
{
return
nil
,
err
}
l2RpcClient
,
err
:=
rpc
.
DialContext
(
context
.
Background
(),
l2RpcURL
)
if
err
!=
nil
{
return
nil
,
err
}
l1GethClient
:=
gethclient
.
New
(
l1RpcClient
)
l2GethClient
:=
gethclient
.
New
(
l2RpcClient
)
log
.
Info
(
"Set up RPC clients"
,
"l1-chain-id"
,
l1ChainID
,
"l2-chain-id"
,
l2ChainID
,
)
return
&
Clients
{
L1Client
:
l1Client
,
L2Client
:
l2Client
,
L1RpcClient
:
l1RpcClient
,
L2RpcClient
:
l2RpcClient
,
L1GethClient
:
l1GethClient
,
L2GethClient
:
l2GethClient
,
},
nil
}
// ClientsFlags represent the flags associated with creating RPC clients.
var
ClientsFlags
=
[]
cli
.
Flag
{
&
cli
.
StringFlag
{
Name
:
"l1-rpc-url"
,
Required
:
true
,
Usage
:
"L1 RPC URL"
,
EnvVars
:
[]
string
{
"L1_RPC_URL"
},
},
&
cli
.
StringFlag
{
Name
:
"l2-rpc-url"
,
Required
:
true
,
Usage
:
"L2 RPC URL"
,
EnvVars
:
[]
string
{
"L2_RPC_URL"
},
},
}
// Addresses represents the address values of various contracts. The values can
// be easily populated via a [cli.Context].
type
Addresses
struct
{
AddressManager
common
.
Address
OptimismPortal
common
.
Address
L1StandardBridge
common
.
Address
L1CrossDomainMessenger
common
.
Address
CanonicalTransactionChain
common
.
Address
StateCommitmentChain
common
.
Address
}
// AddressesFlags represent the flags associated with address parsing.
var
AddressesFlags
=
[]
cli
.
Flag
{
&
cli
.
StringFlag
{
Name
:
"address-manager-address"
,
Usage
:
"AddressManager address"
,
EnvVars
:
[]
string
{
"ADDRESS_MANAGER_ADDRESS"
},
},
&
cli
.
StringFlag
{
Name
:
"optimism-portal-address"
,
Usage
:
"OptimismPortal address"
,
EnvVars
:
[]
string
{
"OPTIMISM_PORTAL_ADDRESS"
},
},
&
cli
.
StringFlag
{
Name
:
"l1-standard-bridge-address"
,
Usage
:
"L1StandardBridge address"
,
EnvVars
:
[]
string
{
"L1_STANDARD_BRIDGE_ADDRESS"
},
},
&
cli
.
StringFlag
{
Name
:
"l1-crossdomain-messenger-address"
,
Usage
:
"L1CrossDomainMessenger address"
,
EnvVars
:
[]
string
{
"L1_CROSSDOMAIN_MESSENGER_ADDRESS"
},
},
&
cli
.
StringFlag
{
Name
:
"canonical-transaction-chain-address"
,
Usage
:
"CanonicalTransactionChain address"
,
EnvVars
:
[]
string
{
"CANONICAL_TRANSACTION_CHAIN_ADDRESS"
},
},
&
cli
.
StringFlag
{
Name
:
"state-commitment-chain-address"
,
Usage
:
"StateCommitmentChain address"
,
EnvVars
:
[]
string
{
"STATE_COMMITMENT_CHAIN_ADDRESS"
},
},
}
// NewAddresses populates an Addresses struct given a [cli.Context].
// This is useful for writing scripts that interact with smart contracts.
func
NewAddresses
(
ctx
*
cli
.
Context
)
(
*
Addresses
,
error
)
{
var
addresses
Addresses
var
err
error
addresses
.
AddressManager
,
err
=
parseAddress
(
ctx
,
"address-manager-address"
)
if
err
!=
nil
{
return
nil
,
err
}
addresses
.
OptimismPortal
,
err
=
parseAddress
(
ctx
,
"optimism-portal-address"
)
if
err
!=
nil
{
return
nil
,
err
}
addresses
.
L1StandardBridge
,
err
=
parseAddress
(
ctx
,
"l1-standard-bridge-address"
)
if
err
!=
nil
{
return
nil
,
err
}
addresses
.
L1CrossDomainMessenger
,
err
=
parseAddress
(
ctx
,
"l1-crossdomain-messenger-address"
)
if
err
!=
nil
{
return
nil
,
err
}
addresses
.
CanonicalTransactionChain
,
err
=
parseAddress
(
ctx
,
"canonical-transaction-chain-address"
)
if
err
!=
nil
{
return
nil
,
err
}
addresses
.
StateCommitmentChain
,
err
=
parseAddress
(
ctx
,
"state-commitment-chain-address"
)
if
err
!=
nil
{
return
nil
,
err
}
return
&
addresses
,
nil
}
// parseAddress will parse a [common.Address] from a [cli.Context] and return
// an error if the configured address is not correct.
func
parseAddress
(
ctx
*
cli
.
Context
,
name
string
)
(
common
.
Address
,
error
)
{
value
:=
ctx
.
String
(
name
)
if
value
==
""
{
return
common
.
Address
{},
nil
}
if
!
common
.
IsHexAddress
(
value
)
{
return
common
.
Address
{},
fmt
.
Errorf
(
"invalid address: %s"
,
value
)
}
return
common
.
HexToAddress
(
value
),
nil
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment