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
71aadcfc
Unverified
Commit
71aadcfc
authored
Jun 12, 2023
by
Mark Tyneway
Committed by
GitHub
Jun 12, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5996 from ethereum-optimism/cleanup/delete-extra-migration-code
op-chain-ops: delete migration code
parents
ff615e77
69b0f46d
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 @
71aadcfc
...
...
@@ -27,11 +27,9 @@ require (
github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/olekukonko/tablewriter v0.0.5
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/urfave/cli v1.22.9
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/sync v0.1.0
golang.org/x/term v0.6.0
...
...
@@ -120,7 +118,6 @@ require (
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // 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/pointerstructure v1.2.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
...
...
@@ -167,6 +164,7 @@ require (
go.uber.org/fx v1.19.1 // indirect
go.uber.org/multierr v1.9.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/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
...
...
go.sum
View file @
71aadcfc
...
...
@@ -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/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/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/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
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
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/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/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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
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/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/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
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
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-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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/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/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
...
...
indexer/api/api_test.go
View file @
71aadcfc
...
...
@@ -5,23 +5,27 @@ import (
"net/http/httptest"
"testing"
"github.com/google/uuid"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
// MockBridgeView mocks the BridgeView interface
type
MockBridgeView
struct
{}
const
(
guid1
=
"8408b6d2-7c90-4cfc-8604-b2204116cb6a"
guid2
=
"8408b6d2-7c90-4cfc-8604-b2204116cb6b"
)
// DepositsByAddress mocks returning deposits by an address
func
(
mbv
*
MockBridgeView
)
DepositsByAddress
(
address
common
.
Address
)
([]
*
database
.
DepositWithTransactionHash
,
error
)
{
return
[]
*
database
.
DepositWithTransactionHash
{
{
Deposit
:
database
.
Deposit
{
GUID
:
uuid
.
New
(
),
InitiatedL1EventGUID
:
"mockEventGUID1"
,
GUID
:
uuid
.
MustParse
(
guid1
),
InitiatedL1EventGUID
:
guid2
,
Tx
:
database
.
Transaction
{},
TokenPair
:
database
.
TokenPair
{},
},
...
...
@@ -35,8 +39,8 @@ func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*data
return
[]
*
database
.
WithdrawalWithTransactionHashes
{
{
Withdrawal
:
database
.
Withdrawal
{
GUID
:
uuid
.
New
(
),
InitiatedL2EventGUID
:
"mockEventGUID2"
,
GUID
:
uuid
.
MustParse
(
guid2
),
InitiatedL2EventGUID
:
guid1
,
WithdrawalHash
:
common
.
HexToHash
(
"0x456"
),
Tx
:
database
.
Transaction
{},
TokenPair
:
database
.
TokenPair
{},
...
...
op-chain-ops/Dockerfile
deleted
100644 → 0
View file @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
71aadcfc
package
crossdomain
import
(
"errors"
"fmt"
"math/big"
...
...
@@ -9,16 +8,9 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"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"
)
var
(
abiTrue
=
common
.
Hash
{
31
:
0x01
}
errLegacyStorageSlotNotFound
=
errors
.
New
(
"cannot find storage slot"
)
)
// Constants used by `CrossDomainMessenger.baseGas`
var
(
RelayConstantOverhead
uint64
=
200
_000
...
...
@@ -30,43 +22,6 @@ var (
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
// style Withdrawal.
func
MigrateWithdrawal
(
...
...
op-chain-ops/crossdomain/params.go
deleted
100644 → 0
View file @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
71aadcfc
package
crossdomain
import
(
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"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
(
// Standard ABI types
Uint256Type
,
_
=
abi
.
NewType
(
"uint256"
,
""
,
nil
)
...
...
@@ -33,35 +21,3 @@ type WithdrawalMessage interface {
Hash
()
(
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
71aadcfc
...
...
@@ -18,6 +18,9 @@ import (
// defaultL2GasLimit represents the default gas limit for an L2 block.
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
func
NewL2Genesis
(
config
*
DeployConfig
,
block
*
types
.
Block
)
(
*
core
.
Genesis
,
error
)
{
if
config
.
L2ChainID
==
0
{
...
...
op-chain-ops/genesis/migration_action/action.go
deleted
100644 → 0
View file @
ff615e77
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 @
71aadcfc
...
...
@@ -26,33 +26,6 @@ var (
predeploys
.
GovernanceTokenAddr
:
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.
...
...
@@ -79,32 +52,6 @@ func SetL1Proxies(db vm.StateDB, proxyAdminAddr common.Address) error {
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
{
depBytecode
,
err
:=
bindings
.
GetDeployedBytecode
(
"Proxy"
)
if
err
!=
nil
{
...
...
op-chain-ops/genesis/setters_test.go
deleted
100644 → 0
View file @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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 @
ff615e77
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