Commit ca810b29 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge pull request #4539 from ethereum-optimism/test/pending-withdrawals-update

op-chain-ops: update script to submit pending withdrawals
parents 1fc3c60f 1c969103
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"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/genesis/migration"
"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/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers"
"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"
)
// TODO(tynes): handle connecting directly to a LevelDB based StateDB
// abiTrue represents the storage representation of the boolean
// value true.
var abiTrue = common.Hash{31: 0x01}
// 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() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{
Name: "withdrawals",
Usage: "fetches all pending withdrawals",
Usage: "submits pending withdrawals",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "l1-rpc-url",
......@@ -33,63 +87,344 @@ func main() {
Usage: "RPC URL for an L2 Node",
},
&cli.StringFlag{
Name: "l1-cross-domain-messenger-address",
Name: "optimism-portal-address",
Usage: "Address of the OptimismPortal on L1",
},
&cli.StringFlag{
Name: "l1-crossdomain-messenger-address",
Usage: "Address of the L1CrossDomainMessenger",
},
&cli.Uint64Flag{
Name: "start",
Usage: "Start height to search for events",
&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.Uint64Flag{
Name: "end",
Usage: "End height to search for events",
&cli.StringFlag{
Name: "private-key",
Usage: "Key to sign transactions with",
},
&cli.StringFlag{
Name: "outfile",
Usage: "Path to output file",
Value: "out.json",
Name: "bad-withdrawals-out",
Value: "bad-withdrawals.json",
Usage: "Path to write JSON file of bad withdrawals to manually inspect",
},
},
Action: func(ctx *cli.Context) error {
l1RpcURL := ctx.String("l1-rpc-url")
l2RpcURL := ctx.String("l2-rpc-url")
clients, err := newClients(ctx)
if err != nil {
return err
}
l1Client, err := ethclient.Dial(l1RpcURL)
// initialize the contract bindings
contracts, err := newContracts(ctx, clients.L1Client, clients.L2Client)
if err != nil {
return err
}
l2Client, err := ethclient.Dial(l2RpcURL)
l1xdmAddr := common.HexToAddress(ctx.String("l1-crossdomain-messenger-address"))
l1ChainID, err := clients.L1Client.ChainID(context.Background())
if err != nil {
return err
}
backends := crossdomain.NewBackends(l1Client, l2Client)
// create the set of withdrawals
wds, err := newWithdrawals(ctx, l1ChainID)
if err != nil {
return err
}
l1xDomainMessenger := ctx.String("l1-cross-domain-messenger-address")
if l1xDomainMessenger == "" {
return errors.New("Must pass in L1CrossDomainMessenger address")
period, err := contracts.OptimismPortal.FINALIZATIONPERIODSECONDS(&bind.CallOpts{})
if err != nil {
return err
}
l1xDomainMessengerAddr := common.HexToAddress(l1xDomainMessenger)
messengers, err := crossdomain.NewMessengers(backends, l1xDomainMessengerAddr)
bedrockStartingBlockNumber, err := contracts.L2OutputOracle.StartingBlockNumber(&bind.CallOpts{})
if err != nil {
return err
}
start := ctx.Uint64("start")
end := ctx.Uint64("end")
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())
// All messages are expected to be version 0 messages
withdrawals, err := crossdomain.GetPendingWithdrawals(messengers, common.Big0, start, end)
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, 0o755)
if err != nil {
return err
}
outfile := ctx.String("outfile")
if err := writeJSONFile(outfile, withdrawals); err != nil {
// 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"))
// iterate over all of the withdrawals and submit them
for i, wd := range wds {
log.Info("Processing withdrawal", "index", i)
// migrate the withdrawal
withdrawal, err := crossdomain.MigrateWithdrawal(wd, &l1xdmAddr)
if err != nil {
return err
}
// Pass to Portal
hash, err := withdrawal.Hash()
if err != nil {
return err
}
lcdm := wd.CrossDomainMessage()
legacyXdmHash, err := lcdm.Hash()
if err != nil {
return err
}
// check to see if the withdrawal has already been successfully
// relayed or received
isSuccess, err := contracts.L1CrossDomainMessenger.SuccessfulMessages(&bind.CallOpts{}, legacyXdmHash)
if err != nil {
return err
}
isFailed, err := contracts.L1CrossDomainMessenger.FailedMessages(&bind.CallOpts{}, legacyXdmHash)
if err != nil {
return err
}
xdmHash := crypto.Keccak256Hash(withdrawal.Data)
if err != nil {
return err
}
isSuccessNew, err := contracts.L1CrossDomainMessenger.SuccessfulMessages(&bind.CallOpts{}, xdmHash)
if err != nil {
return err
}
isFailedNew, err := contracts.L1CrossDomainMessenger.FailedMessages(&bind.CallOpts{}, xdmHash)
if err != nil {
return err
}
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 {
return err
}
// 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, "slot", slot)
continue
}
// 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 {
return err
}
log.Debug("L2ToL1MessagePasser status", "value", common.Bytes2Hex(storageValue))
// the value should be set to a boolean in storage
if !bytes.Equal(storageValue, abiTrue.Bytes()) {
return fmt.Errorf("storage slot %x not found in state", slot)
}
legacySlot, err := wd.StorageSlot()
if err != nil {
return err
}
legacyStorageValue, err := clients.L2Client.StorageAt(context.Background(), predeploys.LegacyMessagePasserAddr, legacySlot, nil)
if err != nil {
return err
}
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 {
return err
}
// if it has not been proven, then prove it
if proven.Timestamp.Cmp(common.Big0) == 0 {
log.Info("Proving withdrawal to OptimismPortal")
if err := proveWithdrawalTransaction(contracts, clients, opts, withdrawal, bedrockStartingBlockNumber, period); err != nil {
return err
}
} else {
log.Info("Withdrawal already proven to OptimismPortal")
}
// check to see if the withdrawal has been finalized already
isFinalized, err := contracts.OptimismPortal.FinalizedWithdrawals(&bind.CallOpts{}, hash)
if err != nil {
return err
}
if !isFinalized {
// Get the ETH balance of the withdrawal target *before* the finalization
targetBalBefore, err := clients.L1Client.BalanceAt(context.Background(), *wd.Target, nil)
if err != nil {
return err
}
log.Debug("Balance before finalization", "balance", targetBalBefore, "account", *wd.Target)
log.Info("Finalizing withdrawal")
receipt, err := finalizeWithdrawalTransaction(contracts, clients, opts, wd, withdrawal)
if err != nil {
return err
}
log.Info("withdrawal finalized", "tx-hash", receipt.TxHash, "withdrawal-hash", hash)
finalizationTrace, err := callTrace(clients, receipt)
if err != nil {
return nil
}
isSuccessNewPost, err := contracts.L1CrossDomainMessenger.SuccessfulMessages(&bind.CallOpts{}, xdmHash)
if err != nil {
return err
}
// This would indicate that there is a replayability problem
if isSuccess && isSuccessNewPost {
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "should revert"); err != nil {
return err
}
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 {
return err
}
continue
}
traceJson, err := json.MarshalIndent(callFrame, "", " ")
if err != nil {
return err
}
log.Debug(fmt.Sprintf("%v", string(traceJson)))
abi, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return err
}
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 {
return err
}
log.Info("decoded calldata", "name", method.Name)
switch method.Name {
case "finalizeERC20Withdrawal":
if err := handleFinalizeERC20Withdrawal(args, receipt, l1StandardBridgeAddress); err != nil {
return err
}
case "finalizeETHWithdrawal":
if err := handleFinalizeETHWithdrawal(args); err != nil {
return err
}
default:
log.Info("Unhandled method", "name", method.Name)
}
}
// Ensure that the target's balance was increasedData correctly
wdValue, err := wd.Value()
if err != nil {
return err
}
if method != nil {
log.Info("withdrawal action", "function", method.Name, "value", wdValue)
} else {
log.Info("unknown method", "to", wd.Target, "data", hexutil.Encode(wd.Data))
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "unknown method"); err != nil {
return err
}
}
// check that the user's intents are actually executed
if common.HexToAddress(callFrame.To) != *wd.Target {
log.Info("target mismatch", "index", i)
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "target mismatch"); err != nil {
return err
}
continue
}
if !bytes.Equal(hexutil.MustDecode(callFrame.Input), wd.Data) {
log.Info("calldata mismatch", "index", i)
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "calldata mismatch"); err != nil {
return err
}
continue
}
if callFrame.BigValue().Cmp(wdValue) != 0 {
log.Info("value mismatch", "index", i)
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "value mismatch"); err != nil {
return err
}
continue
}
// Get the ETH balance of the withdrawal target *after* the finalization
targetBalAfter, err := clients.L1Client.BalanceAt(context.Background(), *wd.Target, nil)
if err != nil {
return err
}
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 {
return err
}
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 {
return err
}
continue
}
} else {
log.Info("Already finalized")
}
}
return nil
},
}
......@@ -99,14 +434,525 @@ func main() {
}
}
func writeJSONFile(outfile string, input interface{}) error {
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
// callTrace will call `debug_traceTransaction` on a remote node
func callTrace(c *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)
if err != nil {
return finalizationTrace, err
}
return finalizationTrace, err
}
// 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 *clients, opts *bind.TransactOpts, withdrawal *crossdomain.Withdrawal, bn, finalizationPeriod *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
}
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)
block, err := cl.L1Client.BlockByHash(context.Background(), receipt.BlockHash)
if err != nil {
return err
}
defer f.Close()
initialTime := block.Time()
for {
log.Info("waiting for finalization")
if block.Time() >= initialTime+finalizationPeriod.Uint64() {
log.Info("can be finalized")
break
}
time.Sleep(1 * time.Second)
block, err = cl.L1Client.BlockByNumber(context.Background(), nil)
if err != nil {
return err
}
}
return nil
}
func finalizeWithdrawalTransaction(
c *contracts,
cl *clients,
opts *bind.TransactOpts,
wd *crossdomain.LegacyWithdrawal,
withdrawal *crossdomain.Withdrawal,
) (*types.Receipt, error) {
if wd.Target == nil {
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) {
optimismPortalAddress := ctx.String("optimism-portal-address")
if len(optimismPortalAddress) == 0 {
return nil, errors.New("OptimismPortal address not configured")
}
optimismPortalAddr := common.HexToAddress(optimismPortalAddress)
portal, err := bindings.NewOptimismPortal(optimismPortalAddr, l1Backend)
if err != nil {
return nil, err
}
l1xdmAddress := ctx.String("l1-crossdomain-messenger-address")
if l1xdmAddress == "" {
return nil, errors.New("L1CrossDomainMessenger address not configured")
}
l1xdmAddr := common.HexToAddress(l1xdmAddress)
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
}
// 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
func newClients(ctx *cli.Context) (*clients, error) {
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
}
l2RpcURL := ctx.String("l2-rpc-url")
l2Client, err := ethclient.Dial(l2RpcURL)
if err != nil {
return nil, err
}
l2ChainID, err := l2Client.ChainID(context.Background())
if err != nil {
return nil, 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
}
// 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")
log.Debug("Migration data", "ovm-path", ovmMsgs, "evm-messages", evmMsgs)
ovmMessages, err := migration.NewSentMessage(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 = []*migration.SentMessage{}
}
evmMessages, err := migration.NewSentMessage(evmMsgs)
if err != nil {
return nil, err
}
migrationData := migration.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.Target
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
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
return enc.Encode(input)
// createOutput will create the data required to send a withdrawal transaction.
func createOutput(
withdrawal *crossdomain.Withdrawal,
oracle *bindings.L2OutputOracle,
blockNumber *big.Int,
clients *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(),
}
// TODO(mark): import the function from `op-node` to compute the hash
// instead of doing this. Will update when testing against mainnet.
localOutputRootHash := crypto.Keccak256Hash(
outputRootProof.Version[:],
outputRootProof.StateRoot[:],
outputRootProof.MessagePasserStorageRoot[:],
outputRootProof.LatestBlockhash[:],
)
// 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
}
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
......@@ -17,7 +18,7 @@ import (
type LegacyWithdrawal struct {
Target *common.Address `json:"target"`
Sender *common.Address `json:"sender"`
Data []byte `json:"data"`
Data hexutil.Bytes `json:"data"`
Nonce *big.Int `json:"nonce"`
}
......@@ -38,7 +39,7 @@ func NewLegacyWithdrawal(target, sender *common.Address, data []byte, nonce *big
// through the standard optimism cross domain messaging system by hashing in
// the L2CrossDomainMessenger address.
func (w *LegacyWithdrawal) Encode() ([]byte, error) {
enc, err := EncodeCrossDomainMessageV0(w.Target, w.Sender, w.Data, w.Nonce)
enc, err := EncodeCrossDomainMessageV0(w.Target, w.Sender, []byte(w.Data), w.Nonce)
if err != nil {
return nil, fmt.Errorf("cannot encode LegacyWithdrawal: %w", err)
}
......@@ -98,7 +99,7 @@ func (w *LegacyWithdrawal) Decode(data []byte) error {
w.Target = &target
w.Sender = &sender
w.Data = msgData
w.Data = hexutil.Bytes(msgData)
w.Nonce = nonce
return nil
}
......
......@@ -72,6 +72,12 @@ func (c *CrossDomainMessage) Hash() (common.Hash, error) {
}
}
// HashV1 forces using the V1 hash even if its a legacy hash. This is used
// for the migration process.
func (c *CrossDomainMessage) HashV1() (common.Hash, error) {
return HashCrossDomainMessageV1(c.Nonce, c.Sender, c.Target, c.Value, c.GasLimit, c.Data)
}
// ToWithdrawal will turn a CrossDomainMessage into a Withdrawal.
// This only works for version 0 CrossDomainMessages as not all of
// the data is present for version 1 CrossDomainMessages to be turned
......
......@@ -273,6 +273,6 @@ func TestGetPendingWithdrawals(t *testing.T) {
withdrawal := withdrawals[i]
require.Equal(t, msg.Target, *withdrawal.Target)
require.Equal(t, msg.Message, withdrawal.Data)
require.Equal(t, msg.Message, []byte(withdrawal.Data))
}
}
......@@ -24,6 +24,7 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/ethereum-optimism/optimism/op-service v0.10.11 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
......@@ -35,6 +36,8 @@ require (
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/huin/goupnp v1.0.3 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
......@@ -46,12 +49,16 @@ require (
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
......
......@@ -94,6 +94,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
......@@ -196,11 +197,14 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
......@@ -374,6 +378,7 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
......@@ -400,6 +405,7 @@ github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
......@@ -473,7 +479,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment