Commit 98e83d63 authored by Diederik Loerakker's avatar Diederik Loerakker Committed by GitHub

Bedrock: share more test utils, cleanup derive package structure, move...

Bedrock: share more test utils, cleanup derive package structure, move payload/attributes types to eth package (#2761)

* feat(bedrock): move ExecutionPayload, PayloadAttributes and other engine types from l2 to eth package

* chore(bedrock): clean up derive package, move test util out or polish for public use, split payload_attributes and improve file names

* chore(bedrock): move more test utils out of derive and driver packages into shared package

* bedrock: fix go import newlines/order

* testutils: describe TestID usage
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent c0c5af0b
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/l2" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/node" "github.com/ethereum-optimism/optimism/op-node/node"
rollupNode "github.com/ethereum-optimism/optimism/op-node/node" rollupNode "github.com/ethereum-optimism/optimism/op-node/node"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/withdrawals" "github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-proposer/rollupclient" "github.com/ethereum-optimism/optimism/op-proposer/rollupclient"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
...@@ -265,7 +264,7 @@ func TestSystemE2E(t *testing.T) { ...@@ -265,7 +264,7 @@ func TestSystemE2E(t *testing.T) {
receipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.L1BlockTime)*time.Second) receipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.L1BlockTime)*time.Second)
require.Nil(t, err, "Waiting for deposit tx on L1") require.Nil(t, err, "Waiting for deposit tx on L1")
reconstructedDep, err := derive.UnmarshalLogEvent(receipt.Logs[0]) reconstructedDep, err := derive.UnmarshalDepositLogEvent(receipt.Logs[0])
require.NoError(t, err, "Could not reconstruct L2 Deposit") require.NoError(t, err, "Could not reconstruct L2 Deposit")
tx = types.NewTx(reconstructedDep) tx = types.NewTx(reconstructedDep)
receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second) receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second)
...@@ -355,7 +354,7 @@ func TestMintOnRevertedDeposit(t *testing.T) { ...@@ -355,7 +354,7 @@ func TestMintOnRevertedDeposit(t *testing.T) {
receipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.L1BlockTime)*time.Second) receipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.L1BlockTime)*time.Second)
require.Nil(t, err, "Waiting for deposit tx on L1") require.Nil(t, err, "Waiting for deposit tx on L1")
reconstructedDep, err := derive.UnmarshalLogEvent(receipt.Logs[0]) reconstructedDep, err := derive.UnmarshalDepositLogEvent(receipt.Logs[0])
require.NoError(t, err, "Could not reconstruct L2 Deposit") require.NoError(t, err, "Could not reconstruct L2 Deposit")
tx = types.NewTx(reconstructedDep) tx = types.NewTx(reconstructedDep)
receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second) receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second)
...@@ -501,10 +500,10 @@ func TestSystemMockP2P(t *testing.T) { ...@@ -501,10 +500,10 @@ func TestSystemMockP2P(t *testing.T) {
var published, received []common.Hash var published, received []common.Hash
seqTracer, verifTracer := new(FnTracer), new(FnTracer) seqTracer, verifTracer := new(FnTracer), new(FnTracer)
seqTracer.OnPublishL2PayloadFn = func(ctx context.Context, payload *l2.ExecutionPayload) { seqTracer.OnPublishL2PayloadFn = func(ctx context.Context, payload *eth.ExecutionPayload) {
published = append(published, payload.BlockHash) published = append(published, payload.BlockHash)
} }
verifTracer.OnUnsafeL2PayloadFn = func(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload) { verifTracer.OnUnsafeL2PayloadFn = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) {
received = append(received, payload.BlockHash) received = append(received, payload.BlockHash)
} }
cfg.Nodes["sequencer"].Tracer = seqTracer cfg.Nodes["sequencer"].Tracer = seqTracer
...@@ -715,7 +714,7 @@ func TestWithdrawals(t *testing.T) { ...@@ -715,7 +714,7 @@ func TestWithdrawals(t *testing.T) {
require.Nil(t, err, "binding withdrawer on L2") require.Nil(t, err, "binding withdrawer on L2")
// Wait for deposit to arrive // Wait for deposit to arrive
reconstructedDep, err := derive.UnmarshalLogEvent(receipt.Logs[0]) reconstructedDep, err := derive.UnmarshalDepositLogEvent(receipt.Logs[0])
require.NoError(t, err, "Could not reconstruct L2 Deposit") require.NoError(t, err, "Could not reconstruct L2 Deposit")
tx = types.NewTx(reconstructedDep) tx = types.NewTx(reconstructedDep)
receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second) receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second)
......
...@@ -4,15 +4,14 @@ import ( ...@@ -4,15 +4,14 @@ import (
"context" "context"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/ethereum-optimism/optimism/op-node/node" "github.com/ethereum-optimism/optimism/op-node/node"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
) )
type FnTracer struct { type FnTracer struct {
OnNewL1HeadFn func(ctx context.Context, sig eth.L1BlockRef) OnNewL1HeadFn func(ctx context.Context, sig eth.L1BlockRef)
OnUnsafeL2PayloadFn func(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload) OnUnsafeL2PayloadFn func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload)
OnPublishL2PayloadFn func(ctx context.Context, payload *l2.ExecutionPayload) OnPublishL2PayloadFn func(ctx context.Context, payload *eth.ExecutionPayload)
} }
func (n *FnTracer) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) { func (n *FnTracer) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) {
...@@ -21,13 +20,13 @@ func (n *FnTracer) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) { ...@@ -21,13 +20,13 @@ func (n *FnTracer) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) {
} }
} }
func (n *FnTracer) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload) { func (n *FnTracer) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) {
if n.OnUnsafeL2PayloadFn != nil { if n.OnUnsafeL2PayloadFn != nil {
n.OnUnsafeL2PayloadFn(ctx, from, payload) n.OnUnsafeL2PayloadFn(ctx, from, payload)
} }
} }
func (n *FnTracer) OnPublishL2Payload(ctx context.Context, payload *l2.ExecutionPayload) { func (n *FnTracer) OnPublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload) {
if n.OnPublishL2PayloadFn != nil { if n.OnPublishL2PayloadFn != nil {
n.OnPublishL2PayloadFn(ctx, payload) n.OnPublishL2PayloadFn(ctx, payload)
} }
......
package l2 package eth
import ( import (
"encoding/binary" "encoding/binary"
......
package l2 package eth
import ( import (
"bytes" "bytes"
......
// Package l2 connects to the L2 execution engine over the Engine API. package eth
package l2
import ( import (
"bytes" "bytes"
...@@ -9,8 +8,6 @@ import ( ...@@ -9,8 +8,6 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/beacon"
...@@ -111,16 +108,16 @@ type ExecutionPayload struct { ...@@ -111,16 +108,16 @@ type ExecutionPayload struct {
Transactions []Data `json:"transactions"` Transactions []Data `json:"transactions"`
} }
func (payload *ExecutionPayload) ID() eth.BlockID { func (payload *ExecutionPayload) ID() BlockID {
return eth.BlockID{Hash: payload.BlockHash, Number: uint64(payload.BlockNumber)} return BlockID{Hash: payload.BlockHash, Number: uint64(payload.BlockNumber)}
} }
func (payload *ExecutionPayload) ParentID() eth.BlockID { func (payload *ExecutionPayload) ParentID() BlockID {
n := uint64(payload.BlockNumber) n := uint64(payload.BlockNumber)
if n > 0 { if n > 0 {
n -= 1 n -= 1
} }
return eth.BlockID{Hash: payload.ParentHash, Number: n} return BlockID{Hash: payload.ParentHash, Number: n}
} }
type rawTransactions []Data type rawTransactions []Data
......
...@@ -38,26 +38,26 @@ func (s *Source) Close() { ...@@ -38,26 +38,26 @@ func (s *Source) Close() {
s.rpc.Close() s.rpc.Close()
} }
func (s *Source) PayloadByHash(ctx context.Context, hash common.Hash) (*ExecutionPayload, error) { func (s *Source) PayloadByHash(ctx context.Context, hash common.Hash) (*eth.ExecutionPayload, error) {
// TODO: we really do not need to parse every single tx and block detail, keeping transactions encoded is faster. // TODO: we really do not need to parse every single tx and block detail, keeping transactions encoded is faster.
block, err := s.client.BlockByHash(ctx, hash) block, err := s.client.BlockByHash(ctx, hash)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve L2 block by hash: %v", err) return nil, fmt.Errorf("failed to retrieve L2 block by hash: %v", err)
} }
payload, err := BlockAsPayload(block) payload, err := eth.BlockAsPayload(block)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read L2 block as payload: %w", err) return nil, fmt.Errorf("failed to read L2 block as payload: %w", err)
} }
return payload, nil return payload, nil
} }
func (s *Source) PayloadByNumber(ctx context.Context, number *big.Int) (*ExecutionPayload, error) { func (s *Source) PayloadByNumber(ctx context.Context, number *big.Int) (*eth.ExecutionPayload, error) {
// TODO: we really do not need to parse every single tx and block detail, keeping transactions encoded is faster. // TODO: we really do not need to parse every single tx and block detail, keeping transactions encoded is faster.
block, err := s.client.BlockByNumber(ctx, number) block, err := s.client.BlockByNumber(ctx, number)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve L2 block by number: %v", err) return nil, fmt.Errorf("failed to retrieve L2 block by number: %v", err)
} }
payload, err := BlockAsPayload(block) payload, err := eth.BlockAsPayload(block)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read L2 block as payload: %w", err) return nil, fmt.Errorf("failed to read L2 block as payload: %w", err)
} }
...@@ -67,12 +67,12 @@ func (s *Source) PayloadByNumber(ctx context.Context, number *big.Int) (*Executi ...@@ -67,12 +67,12 @@ func (s *Source) PayloadByNumber(ctx context.Context, number *big.Int) (*Executi
// ForkchoiceUpdate updates the forkchoice on the execution client. If attributes is not nil, the engine client will also begin building a block // ForkchoiceUpdate updates the forkchoice on the execution client. If attributes is not nil, the engine client will also begin building a block
// based on attributes after the new head block and return the payload ID. // based on attributes after the new head block and return the payload ID.
// May return an error in ForkChoiceResult, but the error is marshalled into the error return // May return an error in ForkChoiceResult, but the error is marshalled into the error return
func (s *Source) ForkchoiceUpdate(ctx context.Context, fc *ForkchoiceState, attributes *PayloadAttributes) (*ForkchoiceUpdatedResult, error) { func (s *Source) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceState, attributes *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
e := s.log.New("state", fc, "attr", attributes) e := s.log.New("state", fc, "attr", attributes)
e.Debug("Sharing forkchoice-updated signal") e.Debug("Sharing forkchoice-updated signal")
fcCtx, cancel := context.WithTimeout(ctx, time.Second*5) fcCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel() defer cancel()
var result ForkchoiceUpdatedResult var result eth.ForkchoiceUpdatedResult
err := s.rpc.CallContext(fcCtx, &result, "engine_forkchoiceUpdatedV1", fc, attributes) err := s.rpc.CallContext(fcCtx, &result, "engine_forkchoiceUpdatedV1", fc, attributes)
if err == nil { if err == nil {
e.Debug("Shared forkchoice-updated signal") e.Debug("Shared forkchoice-updated signal")
...@@ -82,21 +82,21 @@ func (s *Source) ForkchoiceUpdate(ctx context.Context, fc *ForkchoiceState, attr ...@@ -82,21 +82,21 @@ func (s *Source) ForkchoiceUpdate(ctx context.Context, fc *ForkchoiceState, attr
} else { } else {
e = e.New("err", err) e = e.New("err", err)
if rpcErr, ok := err.(rpc.Error); ok { if rpcErr, ok := err.(rpc.Error); ok {
code := ErrorCode(rpcErr.ErrorCode()) code := eth.ErrorCode(rpcErr.ErrorCode())
e.Warn("Unexpected error code in forkchoice-updated response", "code", code) e.Warn("Unexpected error code in forkchoice-updated response", "code", code)
} else { } else {
e.Error("Failed to share forkchoice-updated signal") e.Error("Failed to share forkchoice-updated signal")
} }
} }
switch result.PayloadStatus.Status { switch result.PayloadStatus.Status {
case ExecutionSyncing: case eth.ExecutionSyncing:
return nil, fmt.Errorf("updated forkchoice, but node is syncing: %v", err) return nil, fmt.Errorf("updated forkchoice, but node is syncing: %v", err)
case ExecutionAccepted, ExecutionInvalidTerminalBlock, ExecutionInvalidBlockHash: case eth.ExecutionAccepted, eth.ExecutionInvalidTerminalBlock, eth.ExecutionInvalidBlockHash:
// ACCEPTED, INVALID_TERMINAL_BLOCK, INVALID_BLOCK_HASH are only for execution // ACCEPTED, INVALID_TERMINAL_BLOCK, INVALID_BLOCK_HASH are only for execution
return nil, fmt.Errorf("unexpected %s status, could not update forkchoice: %v", result.PayloadStatus.Status, err) return nil, fmt.Errorf("unexpected %s status, could not update forkchoice: %v", result.PayloadStatus.Status, err)
case ExecutionInvalid: case eth.ExecutionInvalid:
return nil, fmt.Errorf("cannot update forkchoice, block is invalid: %v", err) return nil, fmt.Errorf("cannot update forkchoice, block is invalid: %v", err)
case ExecutionValid: case eth.ExecutionValid:
return &result, nil return &result, nil
default: default:
return nil, fmt.Errorf("unknown forkchoice status on %s: %q, ", fc.SafeBlockHash, string(result.PayloadStatus.Status)) return nil, fmt.Errorf("unknown forkchoice status on %s: %q, ", fc.SafeBlockHash, string(result.PayloadStatus.Status))
...@@ -104,13 +104,13 @@ func (s *Source) ForkchoiceUpdate(ctx context.Context, fc *ForkchoiceState, attr ...@@ -104,13 +104,13 @@ func (s *Source) ForkchoiceUpdate(ctx context.Context, fc *ForkchoiceState, attr
} }
// ExecutePayload executes a built block on the execution engine and returns an error if it was not successful. // ExecutePayload executes a built block on the execution engine and returns an error if it was not successful.
func (s *Source) NewPayload(ctx context.Context, payload *ExecutionPayload) error { func (s *Source) NewPayload(ctx context.Context, payload *eth.ExecutionPayload) error {
e := s.log.New("block_hash", payload.BlockHash) e := s.log.New("block_hash", payload.BlockHash)
e.Debug("sending payload for execution") e.Debug("sending payload for execution")
execCtx, cancel := context.WithTimeout(ctx, time.Second*5) execCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel() defer cancel()
var result PayloadStatusV1 var result eth.PayloadStatusV1
err := s.rpc.CallContext(execCtx, &result, "engine_newPayloadV1", payload) err := s.rpc.CallContext(execCtx, &result, "engine_newPayloadV1", payload)
e.Debug("Received payload execution result", "status", result.Status, "latestValidHash", result.LatestValidHash, "message", result.ValidationError) e.Debug("Received payload execution result", "status", result.Status, "latestValidHash", result.LatestValidHash, "message", result.ValidationError)
if err != nil { if err != nil {
...@@ -119,17 +119,17 @@ func (s *Source) NewPayload(ctx context.Context, payload *ExecutionPayload) erro ...@@ -119,17 +119,17 @@ func (s *Source) NewPayload(ctx context.Context, payload *ExecutionPayload) erro
} }
switch result.Status { switch result.Status {
case ExecutionValid: case eth.ExecutionValid:
return nil return nil
case ExecutionSyncing: case eth.ExecutionSyncing:
return fmt.Errorf("failed to execute payload %s, node is syncing", payload.ID()) return fmt.Errorf("failed to execute payload %s, node is syncing", payload.ID())
case ExecutionInvalid: case eth.ExecutionInvalid:
return fmt.Errorf("execution payload %s was INVALID! Latest valid hash is %s, ignoring bad block: %q", payload.ID(), result.LatestValidHash, result.ValidationError) return fmt.Errorf("execution payload %s was INVALID! Latest valid hash is %s, ignoring bad block: %q", payload.ID(), result.LatestValidHash, result.ValidationError)
case ExecutionInvalidBlockHash: case eth.ExecutionInvalidBlockHash:
return fmt.Errorf("execution payload %s has INVALID BLOCKHASH! %v", payload.BlockHash, result.ValidationError) return fmt.Errorf("execution payload %s has INVALID BLOCKHASH! %v", payload.BlockHash, result.ValidationError)
case ExecutionInvalidTerminalBlock: case eth.ExecutionInvalidTerminalBlock:
return fmt.Errorf("engine is misconfigured. Received invalid-terminal-block error while engine API should be active at genesis. err: %v", result.ValidationError) return fmt.Errorf("engine is misconfigured. Received invalid-terminal-block error while engine API should be active at genesis. err: %v", result.ValidationError)
case ExecutionAccepted: case eth.ExecutionAccepted:
return fmt.Errorf("execution payload cannot be validated yet, latest valid hash is %s", result.LatestValidHash) return fmt.Errorf("execution payload cannot be validated yet, latest valid hash is %s", result.LatestValidHash)
default: default:
return fmt.Errorf("unknown execution status on %s: %q, ", payload.ID(), string(result.Status)) return fmt.Errorf("unknown execution status on %s: %q, ", payload.ID(), string(result.Status))
...@@ -137,16 +137,16 @@ func (s *Source) NewPayload(ctx context.Context, payload *ExecutionPayload) erro ...@@ -137,16 +137,16 @@ func (s *Source) NewPayload(ctx context.Context, payload *ExecutionPayload) erro
} }
// GetPayload gets the execution payload associated with the PayloadId // GetPayload gets the execution payload associated with the PayloadId
func (s *Source) GetPayload(ctx context.Context, payloadId PayloadID) (*ExecutionPayload, error) { func (s *Source) GetPayload(ctx context.Context, payloadId eth.PayloadID) (*eth.ExecutionPayload, error) {
e := s.log.New("payload_id", payloadId) e := s.log.New("payload_id", payloadId)
e.Debug("getting payload") e.Debug("getting payload")
var result ExecutionPayload var result eth.ExecutionPayload
err := s.rpc.CallContext(ctx, &result, "engine_getPayloadV1", payloadId) err := s.rpc.CallContext(ctx, &result, "engine_getPayloadV1", payloadId)
if err != nil { if err != nil {
e = e.New("payload_id", payloadId, "err", err) e = e.New("payload_id", payloadId, "err", err)
if rpcErr, ok := err.(rpc.Error); ok { if rpcErr, ok := err.(rpc.Error); ok {
code := ErrorCode(rpcErr.ErrorCode()) code := eth.ErrorCode(rpcErr.ErrorCode())
if code != UnavailablePayload { if code != eth.UnavailablePayload {
e.Warn("unexpected error code in get-payload response", "code", code) e.Warn("unexpected error code in get-payload response", "code", code)
} else { } else {
e.Warn("unavailable payload in get-payload request") e.Warn("unavailable payload in get-payload request")
...@@ -217,46 +217,6 @@ func blockToBlockRef(block *types.Block, genesis *rollup.Genesis) (eth.L2BlockRe ...@@ -217,46 +217,6 @@ func blockToBlockRef(block *types.Block, genesis *rollup.Genesis) (eth.L2BlockRe
}, nil }, nil
} }
// PayloadToBlockRef extracts the essential L2BlockRef information from an execution payload,
// falling back to genesis information if necessary.
func PayloadToBlockRef(payload *ExecutionPayload, genesis *rollup.Genesis) (eth.L2BlockRef, error) {
var l1Origin eth.BlockID
var sequenceNumber uint64
if uint64(payload.BlockNumber) == genesis.L2.Number {
if payload.BlockHash != genesis.L2.Hash {
return eth.L2BlockRef{}, fmt.Errorf("expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s", genesis.L2.Number, payload.BlockHash, genesis.L2.Hash)
}
l1Origin = genesis.L1
sequenceNumber = 0
} else {
if len(payload.Transactions) == 0 {
return eth.L2BlockRef{}, fmt.Errorf("l2 block is missing L1 info deposit tx, block hash: %s", payload.BlockHash)
}
var tx types.Transaction
if err := tx.UnmarshalBinary(payload.Transactions[0]); err != nil {
return eth.L2BlockRef{}, fmt.Errorf("failed to decode first tx to read l1 info from: %v", err)
}
if tx.Type() != types.DepositTxType {
return eth.L2BlockRef{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type())
}
info, err := derive.L1InfoDepositTxData(tx.Data())
if err != nil {
return eth.L2BlockRef{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %v", err)
}
l1Origin = eth.BlockID{Hash: info.BlockHash, Number: info.Number}
sequenceNumber = info.SequenceNumber
}
return eth.L2BlockRef{
Hash: payload.BlockHash,
Number: uint64(payload.BlockNumber),
ParentHash: payload.ParentHash,
Time: uint64(payload.Timestamp),
L1Origin: l1Origin,
SequenceNumber: sequenceNumber,
}, nil
}
type ReadOnlySource struct { type ReadOnlySource struct {
rpc *rpc.Client // raw RPC client. Used for methods that do not already have bindings rpc *rpc.Client // raw RPC client. Used for methods that do not already have bindings
client *ethclient.Client // go-ethereum's wrapper around the rpc client for the eth namespace client *ethclient.Client // go-ethereum's wrapper around the rpc client for the eth namespace
......
...@@ -6,6 +6,8 @@ import ( ...@@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -20,13 +22,13 @@ import ( ...@@ -20,13 +22,13 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
func ComputeL2OutputRoot(l2OutputRootVersion Bytes32, blockHash common.Hash, blockRoot common.Hash, storageRoot common.Hash) Bytes32 { func ComputeL2OutputRoot(l2OutputRootVersion eth.Bytes32, blockHash common.Hash, blockRoot common.Hash, storageRoot common.Hash) eth.Bytes32 {
var buf bytes.Buffer var buf bytes.Buffer
buf.Write(l2OutputRootVersion[:]) buf.Write(l2OutputRootVersion[:])
buf.Write(blockRoot.Bytes()) buf.Write(blockRoot.Bytes())
buf.Write(storageRoot[:]) buf.Write(storageRoot[:])
buf.Write(blockHash.Bytes()) buf.Write(blockHash.Bytes())
return Bytes32(crypto.Keccak256Hash(buf.Bytes())) return eth.Bytes32(crypto.Keccak256Hash(buf.Bytes()))
} }
type AccountResult struct { type AccountResult struct {
......
...@@ -50,7 +50,7 @@ func newNodeAPI(config *rollup.Config, l2Client l2EthClient, log log.Logger) *no ...@@ -50,7 +50,7 @@ func newNodeAPI(config *rollup.Config, l2Client l2EthClient, log log.Logger) *no
} }
} }
func (n *nodeAPI) OutputAtBlock(ctx context.Context, number rpc.BlockNumber) ([]l2.Bytes32, error) { func (n *nodeAPI) OutputAtBlock(ctx context.Context, number rpc.BlockNumber) ([]eth.Bytes32, error) {
// TODO: rpc.BlockNumber doesn't support the "safe" tag. Need a new type // TODO: rpc.BlockNumber doesn't support the "safe" tag. Need a new type
head, err := n.client.GetBlockHeader(ctx, toBlockNumArg(number)) head, err := n.client.GetBlockHeader(ctx, toBlockNumArg(number))
...@@ -76,10 +76,10 @@ func (n *nodeAPI) OutputAtBlock(ctx context.Context, number rpc.BlockNumber) ([] ...@@ -76,10 +76,10 @@ func (n *nodeAPI) OutputAtBlock(ctx context.Context, number rpc.BlockNumber) ([]
return nil, fmt.Errorf("invalid withdrawal root hash") return nil, fmt.Errorf("invalid withdrawal root hash")
} }
var l2OutputRootVersion l2.Bytes32 // it's zero for now var l2OutputRootVersion eth.Bytes32 // it's zero for now
l2OutputRoot := l2.ComputeL2OutputRoot(l2OutputRootVersion, head.Hash(), head.Root, proof.StorageHash) l2OutputRoot := l2.ComputeL2OutputRoot(l2OutputRootVersion, head.Hash(), head.Root, proof.StorageHash)
return []l2.Bytes32{l2OutputRootVersion, l2OutputRoot}, nil return []eth.Bytes32{l2OutputRootVersion, l2OutputRoot}, nil
} }
func (n *nodeAPI) Version(ctx context.Context) (string, error) { func (n *nodeAPI) Version(ctx context.Context) (string, error) {
......
...@@ -4,24 +4,23 @@ import ( ...@@ -4,24 +4,23 @@ import (
"context" "context"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
) )
// Tracer configures the OpNode to share events // Tracer configures the OpNode to share events
type Tracer interface { type Tracer interface {
OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef)
OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload)
OnPublishL2Payload(ctx context.Context, payload *l2.ExecutionPayload) OnPublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload)
} }
type noOpTracer struct{} type noOpTracer struct{}
func (n noOpTracer) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) {} func (n noOpTracer) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) {}
func (n noOpTracer) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload) { func (n noOpTracer) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) {
} }
func (n noOpTracer) OnPublishL2Payload(ctx context.Context, payload *l2.ExecutionPayload) {} func (n noOpTracer) OnPublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload) {}
var _ Tracer = (*noOpTracer)(nil) var _ Tracer = (*noOpTracer)(nil)
...@@ -215,7 +215,7 @@ func (n *OpNode) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) { ...@@ -215,7 +215,7 @@ func (n *OpNode) OnNewL1Head(ctx context.Context, sig eth.L1BlockRef) {
} }
func (n *OpNode) PublishL2Payload(ctx context.Context, payload *l2.ExecutionPayload) error { func (n *OpNode) PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error {
n.tracer.OnPublishL2Payload(ctx, payload) n.tracer.OnPublishL2Payload(ctx, payload)
// publish to p2p, if we are running p2p at all // publish to p2p, if we are running p2p at all
...@@ -230,7 +230,7 @@ func (n *OpNode) PublishL2Payload(ctx context.Context, payload *l2.ExecutionPayl ...@@ -230,7 +230,7 @@ func (n *OpNode) PublishL2Payload(ctx context.Context, payload *l2.ExecutionPayl
return nil return nil
} }
func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload) error { func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) error {
// ignore if it's from ourselves // ignore if it's from ourselves
if n.p2pNode != nil && from == n.p2pNode.Host().ID() { if n.p2pNode != nil && from == n.p2pNode.Host().ID() {
return nil return nil
......
...@@ -94,7 +94,7 @@ func TestOutputAtBlock(t *testing.T) { ...@@ -94,7 +94,7 @@ func TestOutputAtBlock(t *testing.T) {
client, err := dialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String(), nil) client, err := dialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String(), nil)
assert.NoError(t, err) assert.NoError(t, err)
var out []l2.Bytes32 var out []eth.Bytes32
err = client.CallContext(context.Background(), &out, "optimism_outputAtBlock", "latest") err = client.CallContext(context.Background(), &out, "optimism_outputAtBlock", "latest")
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, out, 2) assert.Len(t, out, 2)
......
...@@ -9,10 +9,11 @@ import ( ...@@ -9,10 +9,11 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -216,7 +217,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config) pubsub.ValidatorEx ...@@ -216,7 +217,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config) pubsub.ValidatorEx
signatureBytes, payloadBytes := data[:65], data[65:] signatureBytes, payloadBytes := data[:65], data[65:]
// [REJECT] if the block encoding is not valid // [REJECT] if the block encoding is not valid
var payload l2.ExecutionPayload var payload eth.ExecutionPayload
if err := payload.UnmarshalSSZ(uint32(len(payloadBytes)), bytes.NewReader(payloadBytes)); err != nil { if err := payload.UnmarshalSSZ(uint32(len(payloadBytes)), bytes.NewReader(payloadBytes)); err != nil {
log.Warn("invalid payload", "err", err, "peer", id) log.Warn("invalid payload", "err", err, "peer", id)
return pubsub.ValidationReject return pubsub.ValidationReject
...@@ -286,7 +287,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config) pubsub.ValidatorEx ...@@ -286,7 +287,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config) pubsub.ValidatorEx
} }
type GossipIn interface { type GossipIn interface {
OnUnsafeL2Payload(ctx context.Context, from peer.ID, msg *l2.ExecutionPayload) error OnUnsafeL2Payload(ctx context.Context, from peer.ID, msg *eth.ExecutionPayload) error
} }
type GossipTopicInfo interface { type GossipTopicInfo interface {
...@@ -295,7 +296,7 @@ type GossipTopicInfo interface { ...@@ -295,7 +296,7 @@ type GossipTopicInfo interface {
type GossipOut interface { type GossipOut interface {
GossipTopicInfo GossipTopicInfo
PublishL2Payload(ctx context.Context, msg *l2.ExecutionPayload, signer Signer) error PublishL2Payload(ctx context.Context, msg *eth.ExecutionPayload, signer Signer) error
Close() error Close() error
} }
...@@ -311,7 +312,7 @@ func (p *publisher) BlocksTopicPeers() []peer.ID { ...@@ -311,7 +312,7 @@ func (p *publisher) BlocksTopicPeers() []peer.ID {
return p.blocksTopic.ListPeers() return p.blocksTopic.ListPeers()
} }
func (p *publisher) PublishL2Payload(ctx context.Context, payload *l2.ExecutionPayload, signer Signer) error { func (p *publisher) PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload, signer Signer) error {
res := msgBufPool.Get().(*[]byte) res := msgBufPool.Get().(*[]byte)
buf := bytes.NewBuffer((*res)[:0]) buf := bytes.NewBuffer((*res)[:0])
defer func() { defer func() {
...@@ -382,9 +383,9 @@ func JoinGossip(p2pCtx context.Context, self peer.ID, ps *pubsub.PubSub, log log ...@@ -382,9 +383,9 @@ func JoinGossip(p2pCtx context.Context, self peer.ID, ps *pubsub.PubSub, log log
type TopicSubscriber func(ctx context.Context, sub *pubsub.Subscription) type TopicSubscriber func(ctx context.Context, sub *pubsub.Subscription)
type MessageHandler func(ctx context.Context, from peer.ID, msg interface{}) error type MessageHandler func(ctx context.Context, from peer.ID, msg interface{}) error
func BlocksHandler(onBlock func(ctx context.Context, from peer.ID, msg *l2.ExecutionPayload) error) MessageHandler { func BlocksHandler(onBlock func(ctx context.Context, from peer.ID, msg *eth.ExecutionPayload) error) MessageHandler {
return func(ctx context.Context, from peer.ID, msg interface{}) error { return func(ctx context.Context, from peer.ID, msg interface{}) error {
payload, ok := msg.(*l2.ExecutionPayload) payload, ok := msg.(*eth.ExecutionPayload)
if !ok { if !ok {
return fmt.Errorf("expected topic validator to parse and validate data into execution payload, but got %T", msg) return fmt.Errorf("expected topic validator to parse and validate data into execution payload, but got %T", msg)
} }
......
...@@ -9,7 +9,8 @@ import ( ...@@ -9,7 +9,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/l2" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -76,10 +77,10 @@ func TestP2PSimple(t *testing.T) { ...@@ -76,10 +77,10 @@ func TestP2PSimple(t *testing.T) {
} }
type mockGossipIn struct { type mockGossipIn struct {
OnUnsafeL2PayloadFn func(ctx context.Context, from peer.ID, msg *l2.ExecutionPayload) error OnUnsafeL2PayloadFn func(ctx context.Context, from peer.ID, msg *eth.ExecutionPayload) error
} }
func (m *mockGossipIn) OnUnsafeL2Payload(ctx context.Context, from peer.ID, msg *l2.ExecutionPayload) error { func (m *mockGossipIn) OnUnsafeL2Payload(ctx context.Context, from peer.ID, msg *eth.ExecutionPayload) error {
if m.OnUnsafeL2PayloadFn != nil { if m.OnUnsafeL2PayloadFn != nil {
return m.OnUnsafeL2PayloadFn(ctx, from, msg) return m.OnUnsafeL2PayloadFn(ctx, from, msg)
} }
......
...@@ -2,231 +2,12 @@ package derive ...@@ -2,231 +2,12 @@ package derive
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"
)
var (
DepositEventABI = "TransactionDeposited(address,address,uint256,uint256,uint64,bool,bytes)"
DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI))
L1InfoFuncSignature = "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64)"
L1InfoFuncBytes4 = crypto.Keccak256([]byte(L1InfoFuncSignature))[:4]
L1InfoDepositerAddress = common.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001")
L1BlockAddress = common.HexToAddress(predeploys.L1Block)
) )
type UserDepositSource struct {
L1BlockHash common.Hash
LogIndex uint64
}
const (
UserDepositSourceDomain = 0
L1InfoDepositSourceDomain = 1
)
func (dep *UserDepositSource) SourceHash() common.Hash {
var input [32 * 2]byte
copy(input[:32], dep.L1BlockHash[:])
binary.BigEndian.PutUint64(input[32*2-8:], dep.LogIndex)
depositIDHash := crypto.Keccak256Hash(input[:])
var domainInput [32 * 2]byte
binary.BigEndian.PutUint64(domainInput[32-8:32], UserDepositSourceDomain)
copy(domainInput[32:], depositIDHash[:])
return crypto.Keccak256Hash(domainInput[:])
}
type L1InfoDepositSource struct {
L1BlockHash common.Hash
SeqNumber uint64
}
func (dep *L1InfoDepositSource) SourceHash() common.Hash {
var input [32 * 2]byte
copy(input[:32], dep.L1BlockHash[:])
binary.BigEndian.PutUint64(input[32*2-8:], dep.SeqNumber)
depositIDHash := crypto.Keccak256Hash(input[:])
var domainInput [32 * 2]byte
binary.BigEndian.PutUint64(domainInput[32-8:32], L1InfoDepositSourceDomain)
copy(domainInput[32:], depositIDHash[:])
return crypto.Keccak256Hash(domainInput[:])
}
// UnmarshalLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data.
//
// parse log data for:
// event TransactionDeposited(
// address indexed from,
// address indexed to,
// uint256 mint,
// uint256 value,
// uint64 gasLimit,
// bool isCreation,
// data data
// );
//
// Additionally, the event log-index and
func UnmarshalLogEvent(ev *types.Log) (*types.DepositTx, error) {
if len(ev.Topics) != 3 {
return nil, fmt.Errorf("expected 3 event topics (event identity, indexed from, indexed to)")
}
if ev.Topics[0] != DepositEventABIHash {
return nil, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash)
}
if len(ev.Data) < 6*32 {
return nil, fmt.Errorf("deposit event data too small (%d bytes): %x", len(ev.Data), ev.Data)
}
var dep types.DepositTx
source := UserDepositSource{
L1BlockHash: ev.BlockHash,
LogIndex: uint64(ev.Index),
}
dep.SourceHash = source.SourceHash()
// indexed 0
dep.From = common.BytesToAddress(ev.Topics[1][12:])
// indexed 1
to := common.BytesToAddress(ev.Topics[2][12:])
// unindexed data
offset := uint64(0)
dep.Mint = new(big.Int).SetBytes(ev.Data[offset : offset+32])
// 0 mint is represented as nil to skip minting code
if dep.Mint.Cmp(new(big.Int)) == 0 {
dep.Mint = nil
}
offset += 32
dep.Value = new(big.Int).SetBytes(ev.Data[offset : offset+32])
offset += 32
gas := new(big.Int).SetBytes(ev.Data[offset : offset+32])
if !gas.IsUint64() {
return nil, fmt.Errorf("bad gas value: %x", ev.Data[offset:offset+32])
}
offset += 32
dep.Gas = gas.Uint64()
// isCreation: If the boolean byte is 1 then dep.To will stay nil,
// and it will create a contract using L2 account nonce to determine the created address.
if ev.Data[offset+31] == 0 {
dep.To = &to
}
offset += 32
// dynamic fields are encoded in three parts. The fixed size portion is the offset of the start of the
// data. The first 32 bytes of a `bytes` object is the length of the bytes. Then are the actual bytes
// padded out to 32 byte increments.
var dataOffset uint256.Int
dataOffset.SetBytes(ev.Data[offset : offset+32])
offset += 32
if !dataOffset.Eq(uint256.NewInt(offset)) {
return nil, fmt.Errorf("incorrect data offset: %v", dataOffset[0])
}
var dataLen uint256.Int
dataLen.SetBytes(ev.Data[offset : offset+32])
offset += 32
if !dataLen.IsUint64() {
return nil, fmt.Errorf("data too large: %s", dataLen.String())
}
// The data may be padded to a multiple of 32 bytes
maxExpectedLen := uint64(len(ev.Data)) - offset
dataLenU64 := dataLen.Uint64()
if dataLenU64 > maxExpectedLen {
return nil, fmt.Errorf("data length too long: %d, expected max %d", dataLenU64, maxExpectedLen)
}
// remaining bytes fill the data
dep.Data = ev.Data[offset : offset+dataLenU64]
return &dep, nil
}
type L1Info interface {
Hash() common.Hash
ParentHash() common.Hash
Root() common.Hash // state-root
NumberU64() uint64
Time() uint64
// MixDigest field, reused for randomness after The Merge (Bellatrix hardfork)
MixDigest() common.Hash
BaseFee() *big.Int
ID() eth.BlockID
BlockRef() eth.L1BlockRef
ReceiptHash() common.Hash
}
// L1InfoDeposit creates a L1 Info deposit transaction based on the L1 block,
// and the L2 block-height difference with the start of the epoch.
func L1InfoDeposit(seqNumber uint64, block L1Info) (*types.DepositTx, error) {
infoDat := L1BlockInfo{
Number: block.NumberU64(),
Time: block.Time(),
BaseFee: block.BaseFee(),
BlockHash: block.Hash(),
SequenceNumber: seqNumber,
}
data, err := infoDat.MarshalBinary()
if err != nil {
return nil, err
}
source := L1InfoDepositSource{
L1BlockHash: block.Hash(),
SeqNumber: seqNumber,
}
// Uses ~30k normal case
// Uses ~70k on first transaction
// Round up to 75k to ensure that we always have enough gas.
return &types.DepositTx{
SourceHash: source.SourceHash(),
From: L1InfoDepositerAddress,
To: &L1BlockAddress,
Mint: nil,
Value: big.NewInt(0),
Gas: 150_000, // TODO: temporary work around. Block 1 seems to require more gas than specced.
Data: data,
}, nil
}
// UserDeposits transforms the L2 block-height and L1 receipts into the transaction inputs for a full L2 block
func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]*types.DepositTx, []error) {
var out []*types.DepositTx
var errs []error
for i, rec := range receipts {
if rec.Status != types.ReceiptStatusSuccessful {
continue
}
for j, log := range rec.Logs {
if log.Address == depositContractAddr && len(log.Topics) > 0 && log.Topics[0] == DepositEventABIHash {
dep, err := UnmarshalLogEvent(log)
if err != nil {
errs = append(errs, fmt.Errorf("malformatted L1 deposit log in receipt %d, log %d: %w", i, j, err))
} else {
out = append(out, dep)
}
}
}
}
return out, errs
}
func BatchesFromEVMTransactions(config *rollup.Config, txLists []types.Transactions) ([]*BatchData, []error) { func BatchesFromEVMTransactions(config *rollup.Config, txLists []types.Transactions) ([]*BatchData, []error) {
var out []*BatchData var out []*BatchData
var errs []error var errs []error
...@@ -300,10 +81,6 @@ func ValidBatch(batch *BatchData, config *rollup.Config, epoch rollup.Epoch, min ...@@ -300,10 +81,6 @@ func ValidBatch(batch *BatchData, config *rollup.Config, epoch rollup.Epoch, min
return true return true
} }
type L2Info interface {
Time() uint64
}
// FillMissingBatches turns a collection of batches to the input batches for a series of blocks // FillMissingBatches turns a collection of batches to the input batches for a series of blocks
func FillMissingBatches(batches []*BatchData, epoch, blockTime, minL2Time, nextL1Time uint64) []*BatchData { func FillMissingBatches(batches []*BatchData, epoch, blockTime, minL2Time, nextL1Time uint64) []*BatchData {
m := make(map[uint64]*BatchData) m := make(map[uint64]*BatchData)
...@@ -337,31 +114,3 @@ func FillMissingBatches(batches []*BatchData, epoch, blockTime, minL2Time, nextL ...@@ -337,31 +114,3 @@ func FillMissingBatches(batches []*BatchData, epoch, blockTime, minL2Time, nextL
} }
return out return out
} }
// L1InfoDepositBytes returns a serialized L1-info attributes transaction.
func L1InfoDepositBytes(seqNumber uint64, l1Info L1Info) (hexutil.Bytes, error) {
dep, err := L1InfoDeposit(seqNumber, l1Info)
if err != nil {
return nil, fmt.Errorf("failed to create L1 info tx: %v", err)
}
l1Tx := types.NewTx(dep)
opaqueL1Tx, err := l1Tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to encode L1 info tx: %v", err)
}
return opaqueL1Tx, nil
}
func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]hexutil.Bytes, []error) {
userDeposits, errs := UserDeposits(receipts, depositContractAddr)
encodedTxs := make([]hexutil.Bytes, 0, len(userDeposits))
for i, tx := range userDeposits {
opaqueTx, err := types.NewTx(tx).MarshalBinary()
if err != nil {
errs = append(errs, fmt.Errorf("failed to encode user tx %d", i))
} else {
encodedTxs = append(encodedTxs, opaqueTx)
}
}
return encodedTxs, errs
}
package derive
import (
"testing"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
type ValidBatchTestCase struct {
Name string
Epoch rollup.Epoch
MinL2Time uint64
MaxL2Time uint64
Batch BatchData
Valid bool
}
func TestValidBatch(t *testing.T) {
testCases := []ValidBatchTestCase{
{
Name: "valid epoch",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 43,
Transactions: []hexutil.Bytes{{0x01, 0x13, 0x37}, {0x02, 0x13, 0x37}},
}},
Valid: true,
},
{
Name: "ignored epoch",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 122,
Timestamp: 43,
Transactions: nil,
}},
Valid: false,
},
{
Name: "too old",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 42,
Transactions: nil,
}},
Valid: false,
},
{
Name: "too new",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 52,
Transactions: nil,
}},
Valid: false,
},
{
Name: "wrong time alignment",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 46,
Transactions: nil,
}},
Valid: false,
},
{
Name: "good time alignment",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 51, // 31 + 2*10
Transactions: nil,
}},
Valid: true,
},
{
Name: "empty tx",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 43,
Transactions: []hexutil.Bytes{{}},
}},
Valid: false,
},
{
Name: "sneaky deposit",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 43,
Transactions: []hexutil.Bytes{{0x01}, {types.DepositTxType, 0x13, 0x37}, {0xc0, 0x13, 0x37}},
}},
Valid: false,
},
}
conf := rollup.Config{
Genesis: rollup.Genesis{
L2Time: 31, // a genesis time that itself does not align to make it more interesting
},
BlockTime: 2,
// other config fields are ignored and can be left empty.
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
got := ValidBatch(&testCase.Batch, &conf, testCase.Epoch, testCase.MinL2Time, testCase.MaxL2Time)
if got != testCase.Valid {
t.Fatalf("case %v was expected to return %v, but got %v", testCase, testCase.Valid, got)
}
})
}
}
package derive
import (
"encoding/binary"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"
)
var (
DepositEventABI = "TransactionDeposited(address,address,uint256,uint256,uint64,bool,bytes)"
DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI))
)
// UnmarshalDepositLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data.
//
// parse log data for:
// event TransactionDeposited(
// address indexed from,
// address indexed to,
// uint256 mint,
// uint256 value,
// uint64 gasLimit,
// bool isCreation,
// data data
// );
//
// Additionally, the event log-index and
func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
if len(ev.Topics) != 3 {
return nil, fmt.Errorf("expected 3 event topics (event identity, indexed from, indexed to)")
}
if ev.Topics[0] != DepositEventABIHash {
return nil, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash)
}
if len(ev.Data) < 6*32 {
return nil, fmt.Errorf("deposit event data too small (%d bytes): %x", len(ev.Data), ev.Data)
}
var dep types.DepositTx
source := UserDepositSource{
L1BlockHash: ev.BlockHash,
LogIndex: uint64(ev.Index),
}
dep.SourceHash = source.SourceHash()
// indexed 0
dep.From = common.BytesToAddress(ev.Topics[1][12:])
// indexed 1
to := common.BytesToAddress(ev.Topics[2][12:])
// unindexed data
offset := uint64(0)
dep.Mint = new(big.Int).SetBytes(ev.Data[offset : offset+32])
// 0 mint is represented as nil to skip minting code
if dep.Mint.Cmp(new(big.Int)) == 0 {
dep.Mint = nil
}
offset += 32
dep.Value = new(big.Int).SetBytes(ev.Data[offset : offset+32])
offset += 32
gas := new(big.Int).SetBytes(ev.Data[offset : offset+32])
if !gas.IsUint64() {
return nil, fmt.Errorf("bad gas value: %x", ev.Data[offset:offset+32])
}
offset += 32
dep.Gas = gas.Uint64()
// isCreation: If the boolean byte is 1 then dep.To will stay nil,
// and it will create a contract using L2 account nonce to determine the created address.
if ev.Data[offset+31] == 0 {
dep.To = &to
}
offset += 32
// dynamic fields are encoded in three parts. The fixed size portion is the offset of the start of the
// data. The first 32 bytes of a `bytes` object is the length of the bytes. Then are the actual bytes
// padded out to 32 byte increments.
var dataOffset uint256.Int
dataOffset.SetBytes(ev.Data[offset : offset+32])
offset += 32
if !dataOffset.Eq(uint256.NewInt(offset)) {
return nil, fmt.Errorf("incorrect data offset: %v", dataOffset[0])
}
var dataLen uint256.Int
dataLen.SetBytes(ev.Data[offset : offset+32])
offset += 32
if !dataLen.IsUint64() {
return nil, fmt.Errorf("data too large: %s", dataLen.String())
}
// The data may be padded to a multiple of 32 bytes
maxExpectedLen := uint64(len(ev.Data)) - offset
dataLenU64 := dataLen.Uint64()
if dataLenU64 > maxExpectedLen {
return nil, fmt.Errorf("data length too long: %d, expected max %d", dataLenU64, maxExpectedLen)
}
// remaining bytes fill the data
dep.Data = ev.Data[offset : offset+dataLenU64]
return &dep, nil
}
// MarshalDepositLogEvent returns an EVM log entry that encodes a TransactionDeposited event from the deposit contract.
// This is the reverse of the deposit transaction derivation.
func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.DepositTx) *types.Log {
toBytes := common.Hash{}
if deposit.To != nil {
toBytes = deposit.To.Hash()
}
topics := []common.Hash{
DepositEventABIHash,
deposit.From.Hash(),
toBytes,
}
data := make([]byte, 6*32)
offset := 0
if deposit.Mint != nil {
deposit.Mint.FillBytes(data[offset : offset+32])
}
offset += 32
deposit.Value.FillBytes(data[offset : offset+32])
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], deposit.Gas)
offset += 32
if deposit.To == nil { // isCreation
data[offset+31] = 1
}
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], 5*32)
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], uint64(len(deposit.Data)))
data = append(data, deposit.Data...)
if len(data)%32 != 0 { // pad to multiple of 32
data = append(data, make([]byte, 32-(len(data)%32))...)
}
return &types.Log{
Address: depositContractAddr,
Topics: topics,
Data: data,
Removed: false,
// ignored (zeroed):
BlockNumber: 0,
TxHash: common.Hash{},
TxIndex: 0,
BlockHash: common.Hash{},
Index: 0,
}
}
package derive package derive
import ( import (
"encoding/binary"
"fmt" "fmt"
"math/big"
"math/rand" "math/rand"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/assert"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/assert"
) )
func GenerateAddress(rng *rand.Rand) (out common.Address) {
rng.Read(out[:])
return
}
func RandETH(rng *rand.Rand, max int64) *big.Int {
x := big.NewInt(rng.Int63n(max))
x = new(big.Int).Mul(x, big.NewInt(1e18))
return x
}
// Returns a DepositEvent customized on the basis of the id parameter.
func GenerateDeposit(source UserDepositSource, rng *rand.Rand) *types.DepositTx {
dataLen := rng.Int63n(10_000)
data := make([]byte, dataLen)
rng.Read(data)
var to *common.Address
if rng.Intn(2) == 0 {
x := GenerateAddress(rng)
to = &x
}
var mint *big.Int
if rng.Intn(2) == 0 {
mint = RandETH(rng, 200)
}
dep := &types.DepositTx{
SourceHash: source.SourceHash(),
From: GenerateAddress(rng),
To: to,
Value: RandETH(rng, 200),
Gas: uint64(rng.Int63n(10 * 1e6)), // 10 M gas max
Data: data,
Mint: mint,
}
return dep
}
// Generates an EVM log entry that encodes a TransactionDeposited event from the deposit contract.
// Calls GenerateDeposit with random number generator to generate the deposit.
func GenerateDepositLog(deposit *types.DepositTx) *types.Log {
toBytes := common.Hash{}
if deposit.To != nil {
toBytes = deposit.To.Hash()
}
topics := []common.Hash{
DepositEventABIHash,
deposit.From.Hash(),
toBytes,
}
data := make([]byte, 6*32)
offset := 0
if deposit.Mint != nil {
deposit.Mint.FillBytes(data[offset : offset+32])
}
offset += 32
deposit.Value.FillBytes(data[offset : offset+32])
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], deposit.Gas)
offset += 32
if deposit.To == nil { // isCreation
data[offset+31] = 1
}
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], 5*32)
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], uint64(len(deposit.Data)))
data = append(data, deposit.Data...)
if len(data)%32 != 0 { // pad to multiple of 32
data = append(data, make([]byte, 32-(len(data)%32))...)
}
return GenerateLog(MockDepositContractAddr, topics, data)
}
// Generates an EVM log entry with the given topics and data.
func GenerateLog(addr common.Address, topics []common.Hash, data []byte) *types.Log {
return &types.Log{
Address: addr,
Topics: topics,
Data: data,
Removed: false,
// ignored (zeroed):
BlockNumber: 0,
TxHash: common.Hash{},
TxIndex: 0,
BlockHash: common.Hash{},
Index: 0,
}
}
func TestUnmarshalLogEvent(t *testing.T) { func TestUnmarshalLogEvent(t *testing.T) {
for i := int64(0); i < 100; i++ { for i := int64(0); i < 100; i++ {
t.Run(fmt.Sprintf("random_deposit_%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("random_deposit_%d", i), func(t *testing.T) {
rng := rand.New(rand.NewSource(1234 + i)) rng := rand.New(rand.NewSource(1234 + i))
source := UserDepositSource{ source := UserDepositSource{
L1BlockHash: randomHash(rng), L1BlockHash: testutils.RandomHash(rng),
LogIndex: uint64(rng.Intn(10000)), LogIndex: uint64(rng.Intn(10000)),
} }
depInput := GenerateDeposit(source, rng) depInput := testutils.GenerateDeposit(source.SourceHash(), rng)
log := GenerateDepositLog(depInput) log := MarshalDepositLogEvent(MockDepositContractAddr, depInput)
log.TxIndex = uint(rng.Intn(10000)) log.TxIndex = uint(rng.Intn(10000))
log.Index = uint(source.LogIndex) log.Index = uint(source.LogIndex)
log.BlockHash = source.L1BlockHash log.BlockHash = source.L1BlockHash
depOutput, err := UnmarshalLogEvent(log) depOutput, err := UnmarshalDepositLogEvent(log)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -170,7 +67,7 @@ func TestDeriveUserDeposits(t *testing.T) { ...@@ -170,7 +67,7 @@ func TestDeriveUserDeposits(t *testing.T) {
var receipts []*types.Receipt var receipts []*types.Receipt
var expectedDeposits []*types.DepositTx var expectedDeposits []*types.DepositTx
logIndex := uint(0) logIndex := uint(0)
blockHash := randomHash(rng) blockHash := testutils.RandomHash(rng)
for txIndex, rData := range testCase.receipts { for txIndex, rData := range testCase.receipts {
var logs []*types.Log var logs []*types.Log
status := types.ReceiptStatusSuccessful status := types.ReceiptStatusSuccessful
...@@ -181,13 +78,13 @@ func TestDeriveUserDeposits(t *testing.T) { ...@@ -181,13 +78,13 @@ func TestDeriveUserDeposits(t *testing.T) {
var ev *types.Log var ev *types.Log
if isDeposit { if isDeposit {
source := UserDepositSource{L1BlockHash: blockHash, LogIndex: uint64(logIndex)} source := UserDepositSource{L1BlockHash: blockHash, LogIndex: uint64(logIndex)}
dep := GenerateDeposit(source, rng) dep := testutils.GenerateDeposit(source.SourceHash(), rng)
if status == types.ReceiptStatusSuccessful { if status == types.ReceiptStatusSuccessful {
expectedDeposits = append(expectedDeposits, dep) expectedDeposits = append(expectedDeposits, dep)
} }
ev = GenerateDepositLog(dep) ev = MarshalDepositLogEvent(MockDepositContractAddr, dep)
} else { } else {
ev = GenerateLog(GenerateAddress(rng), nil, nil) ev = testutils.GenerateLog(testutils.RandomAddress(rng), nil, nil)
} }
ev.TxIndex = uint(txIndex) ev.TxIndex = uint(txIndex)
ev.Index = logIndex ev.Index = logIndex
...@@ -214,128 +111,3 @@ func TestDeriveUserDeposits(t *testing.T) { ...@@ -214,128 +111,3 @@ func TestDeriveUserDeposits(t *testing.T) {
}) })
} }
} }
type ValidBatchTestCase struct {
Name string
Epoch rollup.Epoch
MinL2Time uint64
MaxL2Time uint64
Batch BatchData
Valid bool
}
func TestValidBatch(t *testing.T) {
testCases := []ValidBatchTestCase{
{
Name: "valid epoch",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 43,
Transactions: []hexutil.Bytes{{0x01, 0x13, 0x37}, {0x02, 0x13, 0x37}},
}},
Valid: true,
},
{
Name: "ignored epoch",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 122,
Timestamp: 43,
Transactions: nil,
}},
Valid: false,
},
{
Name: "too old",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 42,
Transactions: nil,
}},
Valid: false,
},
{
Name: "too new",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 52,
Transactions: nil,
}},
Valid: false,
},
{
Name: "wrong time alignment",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 46,
Transactions: nil,
}},
Valid: false,
},
{
Name: "good time alignment",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 51, // 31 + 2*10
Transactions: nil,
}},
Valid: true,
},
{
Name: "empty tx",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 43,
Transactions: []hexutil.Bytes{{}},
}},
Valid: false,
},
{
Name: "sneaky deposit",
Epoch: 123,
MinL2Time: 43,
MaxL2Time: 52,
Batch: BatchData{BatchV1: BatchV1{
Epoch: 123,
Timestamp: 43,
Transactions: []hexutil.Bytes{{0x01}, {types.DepositTxType, 0x13, 0x37}, {0xc0, 0x13, 0x37}},
}},
Valid: false,
},
}
conf := rollup.Config{
Genesis: rollup.Genesis{
L2Time: 31, // a genesis time that itself does not align to make it more interesting
},
BlockTime: 2,
// other config fields are ignored and can be left empty.
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
got := ValidBatch(&testCase.Batch, &conf, testCase.Epoch, testCase.MinL2Time, testCase.MaxL2Time)
if got != testCase.Valid {
t.Fatalf("case %v was expected to return %v, but got %v", testCase, testCase.Valid, got)
}
})
}
}
package derive
import (
"encoding/binary"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
type UserDepositSource struct {
L1BlockHash common.Hash
LogIndex uint64
}
const (
UserDepositSourceDomain = 0
L1InfoDepositSourceDomain = 1
)
func (dep *UserDepositSource) SourceHash() common.Hash {
var input [32 * 2]byte
copy(input[:32], dep.L1BlockHash[:])
binary.BigEndian.PutUint64(input[32*2-8:], dep.LogIndex)
depositIDHash := crypto.Keccak256Hash(input[:])
var domainInput [32 * 2]byte
binary.BigEndian.PutUint64(domainInput[32-8:32], UserDepositSourceDomain)
copy(domainInput[32:], depositIDHash[:])
return crypto.Keccak256Hash(domainInput[:])
}
type L1InfoDepositSource struct {
L1BlockHash common.Hash
SeqNumber uint64
}
func (dep *L1InfoDepositSource) SourceHash() common.Hash {
var input [32 * 2]byte
copy(input[:32], dep.L1BlockHash[:])
binary.BigEndian.PutUint64(input[32*2-8:], dep.SeqNumber)
depositIDHash := crypto.Keccak256Hash(input[:])
var domainInput [32 * 2]byte
binary.BigEndian.PutUint64(domainInput[32-8:32], L1InfoDepositSourceDomain)
copy(domainInput[32:], depositIDHash[:])
return crypto.Keccak256Hash(domainInput[:])
}
package derive
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
// UserDeposits transforms the L2 block-height and L1 receipts into the transaction inputs for a full L2 block
func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]*types.DepositTx, []error) {
var out []*types.DepositTx
var errs []error
for i, rec := range receipts {
if rec.Status != types.ReceiptStatusSuccessful {
continue
}
for j, log := range rec.Logs {
if log.Address == depositContractAddr && len(log.Topics) > 0 && log.Topics[0] == DepositEventABIHash {
dep, err := UnmarshalDepositLogEvent(log)
if err != nil {
errs = append(errs, fmt.Errorf("malformatted L1 deposit log in receipt %d, log %d: %w", i, j, err))
} else {
out = append(out, dep)
}
}
}
}
return out, errs
}
func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]hexutil.Bytes, []error) {
userDeposits, errs := UserDeposits(receipts, depositContractAddr)
encodedTxs := make([]hexutil.Bytes, 0, len(userDeposits))
for i, tx := range userDeposits {
opaqueTx, err := types.NewTx(tx).MarshalBinary()
if err != nil {
errs = append(errs, fmt.Errorf("failed to encode user tx %d", i))
} else {
encodedTxs = append(encodedTxs, opaqueTx)
}
}
return encodedTxs, errs
}
...@@ -3,14 +3,13 @@ ...@@ -3,14 +3,13 @@
// turned back into L1 data. // turned back into L1 data.
// //
// The flow is data is as follows // The flow is data is as follows
// receipts, batches -> l2.PayloadAttributes with `payload_attributes.go` // receipts, batches -> eth.PayloadAttributes, by parsing the L1 data and deriving L2 inputs
// l2.PayloadAttributes -> l2.ExecutionPayload with `execution_payload.go` // l2.PayloadAttributes -> l2.ExecutionPayload, by running the EVM (using an Execution Engine)
// L2 block -> Corresponding L1 block info with `l1_block_info.go` // L2 block -> Corresponding L1 block info, by parsing the first deposited transaction
// //
// The Payload Attributes derivation stage is a pure function. // The Payload Attributes derivation stage is a pure function.
// The Execution Payload derivation stage relies on the L2 execution engine to perform the // The Execution Payload derivation stage relies on the L2 execution engine to perform the state update.
// state update.
// The inversion step is a pure function. // The inversion step is a pure function.
// //
// The steps should be keep separate to enable easier testing. // The steps should be kept separate to enable easier testing.
package derive package derive
...@@ -125,7 +125,7 @@ func FuzzL1InfoAgainstContract(f *testing.F) { ...@@ -125,7 +125,7 @@ func FuzzL1InfoAgainstContract(f *testing.F) {
} }
// FuzzUnmarshallLogEvent runs a deposit event through the EVM and checks that output of the abigen parsing matches // FuzzUnmarshallLogEvent runs a deposit event through the EVM and checks that output of the abigen parsing matches
// what was inputted and what we parsed during the UnmarshalLogEvent function (which turns it into a deposit tx) // what was inputted and what we parsed during the UnmarshalDepositLogEvent function (which turns it into a deposit tx)
// The purpose is to check that we can never create a transaction that emits a log that we cannot parse as well // The purpose is to check that we can never create a transaction that emits a log that we cannot parse as well
// as ensuring that our custom marshalling matches abigen. // as ensuring that our custom marshalling matches abigen.
func FuzzUnmarshallLogEvent(f *testing.F) { func FuzzUnmarshallLogEvent(f *testing.F) {
...@@ -202,7 +202,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) { ...@@ -202,7 +202,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) {
depositEvent.Raw = types.Log{} // Clear out the log depositEvent.Raw = types.Log{} // Clear out the log
// Verify that is passes our custom unmarshalling logic // Verify that is passes our custom unmarshalling logic
dep, err := UnmarshalLogEvent(logs[0]) dep, err := UnmarshalDepositLogEvent(logs[0])
if err != nil { if err != nil {
t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err) t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err)
} }
......
...@@ -6,9 +6,34 @@ import ( ...@@ -6,9 +6,34 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
) )
var (
L1InfoFuncSignature = "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64)"
L1InfoFuncBytes4 = crypto.Keccak256([]byte(L1InfoFuncSignature))[:4]
L1InfoDepositerAddress = common.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001")
L1BlockAddress = common.HexToAddress(predeploys.L1Block)
)
type L1Info interface {
Hash() common.Hash
ParentHash() common.Hash
Root() common.Hash // state-root
NumberU64() uint64
Time() uint64
// MixDigest field, reused for randomness after The Merge (Bellatrix hardfork)
MixDigest() common.Hash
BaseFee() *big.Int
ID() eth.BlockID
BlockRef() eth.L1BlockRef
ReceiptHash() common.Hash
}
// L1BlockInfo presents the information stored in a L1Block.setL1BlockValues call // L1BlockInfo presents the information stored in a L1Block.setL1BlockValues call
type L1BlockInfo struct { type L1BlockInfo struct {
Number uint64 Number uint64
...@@ -70,3 +95,50 @@ func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) { ...@@ -70,3 +95,50 @@ func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) {
err := info.UnmarshalBinary(data) err := info.UnmarshalBinary(data)
return info, err return info, err
} }
// L1InfoDeposit creates a L1 Info deposit transaction based on the L1 block,
// and the L2 block-height difference with the start of the epoch.
func L1InfoDeposit(seqNumber uint64, block L1Info) (*types.DepositTx, error) {
infoDat := L1BlockInfo{
Number: block.NumberU64(),
Time: block.Time(),
BaseFee: block.BaseFee(),
BlockHash: block.Hash(),
SequenceNumber: seqNumber,
}
data, err := infoDat.MarshalBinary()
if err != nil {
return nil, err
}
source := L1InfoDepositSource{
L1BlockHash: block.Hash(),
SeqNumber: seqNumber,
}
// Uses ~30k normal case
// Uses ~70k on first transaction
// Round up to 75k to ensure that we always have enough gas.
return &types.DepositTx{
SourceHash: source.SourceHash(),
From: L1InfoDepositerAddress,
To: &L1BlockAddress,
Mint: nil,
Value: big.NewInt(0),
Gas: 150_000, // TODO: temporary work around. Block 1 seems to require more gas than specced.
Data: data,
}, nil
}
// L1InfoDepositBytes returns a serialized L1-info attributes transaction.
func L1InfoDepositBytes(seqNumber uint64, l1Info L1Info) ([]byte, error) {
dep, err := L1InfoDeposit(seqNumber, l1Info)
if err != nil {
return nil, fmt.Errorf("failed to create L1 info tx: %v", err)
}
l1Tx := types.NewTx(dep)
opaqueL1Tx, err := l1Tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to encode L1 info tx: %v", err)
}
return opaqueL1Tx, nil
}
...@@ -5,106 +5,17 @@ import ( ...@@ -5,106 +5,17 @@ import (
"math/rand" "math/rand"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
type l1MockInfo struct { var _ L1Info = (*testutils.MockL1Info)(nil)
hash common.Hash
parentHash common.Hash
root common.Hash
num uint64
time uint64
mixDigest [32]byte
baseFee *big.Int
receiptRoot common.Hash
sequenceNumber uint64
}
func (l *l1MockInfo) Hash() common.Hash {
return l.hash
}
func (l *l1MockInfo) ParentHash() common.Hash {
return l.parentHash
}
func (l *l1MockInfo) Root() common.Hash {
return l.root
}
func (l *l1MockInfo) NumberU64() uint64 {
return l.num
}
func (l *l1MockInfo) Time() uint64 {
return l.time
}
func (l *l1MockInfo) MixDigest() common.Hash {
return l.mixDigest
}
func (l *l1MockInfo) BaseFee() *big.Int {
return l.baseFee
}
func (l *l1MockInfo) ReceiptHash() common.Hash {
return l.receiptRoot
}
func (l *l1MockInfo) ID() eth.BlockID {
return eth.BlockID{Hash: l.hash, Number: l.num}
}
func (l *l1MockInfo) BlockRef() eth.L1BlockRef {
return eth.L1BlockRef{
Hash: l.hash,
Number: l.num,
ParentHash: l.parentHash,
Time: l.time,
}
}
func randomHash(rng *rand.Rand) (out common.Hash) {
rng.Read(out[:])
return
}
func randomL1Info(rng *rand.Rand) *l1MockInfo {
return &l1MockInfo{
parentHash: randomHash(rng),
num: rng.Uint64(),
time: rng.Uint64(),
hash: randomHash(rng),
baseFee: big.NewInt(rng.Int63n(1000_000 * 1e9)), // a million GWEI
receiptRoot: types.EmptyRootHash,
root: randomHash(rng),
sequenceNumber: rng.Uint64(),
}
}
func makeInfo(fn func(l *l1MockInfo)) func(rng *rand.Rand) *l1MockInfo {
return func(rng *rand.Rand) *l1MockInfo {
l := randomL1Info(rng)
if fn != nil {
fn(l)
}
return l
}
}
var _ L1Info = (*l1MockInfo)(nil)
type infoTest struct { type infoTest struct {
name string name string
mkInfo func(rng *rand.Rand) *l1MockInfo mkInfo func(rng *rand.Rand) *testutils.MockL1Info
} }
var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeef00000000") var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeef00000000")
...@@ -112,35 +23,35 @@ var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdea ...@@ -112,35 +23,35 @@ var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdea
func TestParseL1InfoDepositTxData(t *testing.T) { func TestParseL1InfoDepositTxData(t *testing.T) {
// Go 1.18 will have native fuzzing for us to use, until then, we cover just the below cases // Go 1.18 will have native fuzzing for us to use, until then, we cover just the below cases
cases := []infoTest{ cases := []infoTest{
{"random", makeInfo(nil)}, {"random", testutils.MakeL1Info(nil)},
{"zero basefee", makeInfo(func(l *l1MockInfo) { {"zero basefee", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.baseFee = new(big.Int) l.InfoBaseFee = new(big.Int)
})}, })},
{"zero time", makeInfo(func(l *l1MockInfo) { {"zero time", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.time = 0 l.InfoTime = 0
})}, })},
{"zero num", makeInfo(func(l *l1MockInfo) { {"zero num", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.num = 0 l.InfoNum = 0
})}, })},
{"zero seq", makeInfo(func(l *l1MockInfo) { {"zero seq", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.sequenceNumber = 0 l.InfoSequenceNumber = 0
})}, })},
{"all zero", func(rng *rand.Rand) *l1MockInfo { {"all zero", func(rng *rand.Rand) *testutils.MockL1Info {
return &l1MockInfo{baseFee: new(big.Int)} return &testutils.MockL1Info{InfoBaseFee: new(big.Int)}
}}, }},
} }
for i, testCase := range cases { for i, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
info := testCase.mkInfo(rand.New(rand.NewSource(int64(1234 + i)))) info := testCase.mkInfo(rand.New(rand.NewSource(int64(1234 + i))))
depTx, err := L1InfoDeposit(123, info) depTx, err := L1InfoDeposit(info.SequenceNumber(), info)
require.NoError(t, err) require.NoError(t, err)
res, err := L1InfoDepositTxData(depTx.Data) res, err := L1InfoDepositTxData(depTx.Data)
require.NoError(t, err, "expected valid deposit info") require.NoError(t, err, "expected valid deposit info")
assert.Equal(t, res.Number, info.num) assert.Equal(t, res.Number, info.NumberU64())
assert.Equal(t, res.Time, info.time) assert.Equal(t, res.Time, info.Time())
assert.True(t, res.BaseFee.Sign() >= 0) assert.True(t, res.BaseFee.Sign() >= 0)
assert.Equal(t, res.BaseFee.Bytes(), info.baseFee.Bytes()) assert.Equal(t, res.BaseFee.Bytes(), info.BaseFee().Bytes())
assert.Equal(t, res.BlockHash, info.hash) assert.Equal(t, res.BlockHash, info.Hash())
}) })
} }
t.Run("no data", func(t *testing.T) { t.Run("no data", func(t *testing.T) {
......
package derive
import (
"fmt"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/core/types"
)
// PayloadToBlockRef extracts the essential L2BlockRef information from an execution payload,
// falling back to genesis information if necessary.
func PayloadToBlockRef(payload *eth.ExecutionPayload, genesis *rollup.Genesis) (eth.L2BlockRef, error) {
var l1Origin eth.BlockID
var sequenceNumber uint64
if uint64(payload.BlockNumber) == genesis.L2.Number {
if payload.BlockHash != genesis.L2.Hash {
return eth.L2BlockRef{}, fmt.Errorf("expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s", genesis.L2.Number, payload.BlockHash, genesis.L2.Hash)
}
l1Origin = genesis.L1
sequenceNumber = 0
} else {
if len(payload.Transactions) == 0 {
return eth.L2BlockRef{}, fmt.Errorf("l2 block is missing L1 info deposit tx, block hash: %s", payload.BlockHash)
}
var tx types.Transaction
if err := tx.UnmarshalBinary(payload.Transactions[0]); err != nil {
return eth.L2BlockRef{}, fmt.Errorf("failed to decode first tx to read l1 info from: %v", err)
}
if tx.Type() != types.DepositTxType {
return eth.L2BlockRef{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type())
}
info, err := L1InfoDepositTxData(tx.Data())
if err != nil {
return eth.L2BlockRef{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %v", err)
}
l1Origin = eth.BlockID{Hash: info.BlockHash, Number: info.Number}
sequenceNumber = info.SequenceNumber
}
return eth.L2BlockRef{
Hash: payload.BlockHash,
Number: uint64(payload.BlockNumber),
ParentHash: payload.ParentHash,
Time: uint64(payload.Timestamp),
L1Origin: l1Origin,
SequenceNumber: sequenceNumber,
}, nil
}
...@@ -29,11 +29,11 @@ type Downloader interface { ...@@ -29,11 +29,11 @@ type Downloader interface {
} }
type Engine interface { type Engine interface {
GetPayload(ctx context.Context, payloadId l2.PayloadID) (*l2.ExecutionPayload, error) GetPayload(ctx context.Context, payloadId eth.PayloadID) (*eth.ExecutionPayload, error)
ForkchoiceUpdate(ctx context.Context, state *l2.ForkchoiceState, attr *l2.PayloadAttributes) (*l2.ForkchoiceUpdatedResult, error) ForkchoiceUpdate(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error)
NewPayload(ctx context.Context, payload *l2.ExecutionPayload) error NewPayload(ctx context.Context, payload *eth.ExecutionPayload) error
PayloadByHash(context.Context, common.Hash) (*l2.ExecutionPayload, error) PayloadByHash(context.Context, common.Hash) (*eth.ExecutionPayload, error)
PayloadByNumber(context.Context, *big.Int) (*l2.ExecutionPayload, error) PayloadByNumber(context.Context, *big.Int) (*eth.ExecutionPayload, error)
} }
type L1Chain interface { type L1Chain interface {
...@@ -44,7 +44,7 @@ type L1Chain interface { ...@@ -44,7 +44,7 @@ type L1Chain interface {
} }
type L2Chain interface { type L2Chain interface {
ForkchoiceUpdate(ctx context.Context, state *l2.ForkchoiceState, attr *l2.PayloadAttributes) (*l2.ForkchoiceUpdatedResult, error) ForkchoiceUpdate(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error)
L2BlockRefByNumber(ctx context.Context, l2Num *big.Int) (eth.L2BlockRef, error) L2BlockRefByNumber(ctx context.Context, l2Num *big.Int) (eth.L2BlockRef, error)
L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error) L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error)
} }
...@@ -55,15 +55,15 @@ type outputInterface interface { ...@@ -55,15 +55,15 @@ type outputInterface interface {
insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.L2BlockRef, l2Finalized eth.BlockID, l1Input []eth.BlockID) (eth.L2BlockRef, eth.L2BlockRef, bool, error) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.L2BlockRef, l2Finalized eth.BlockID, l1Input []eth.BlockID) (eth.L2BlockRef, eth.L2BlockRef, bool, error)
// createNewBlock builds a new block based on the L2 Head, L1 Origin, and the current mempool. // createNewBlock builds a new block based on the L2 Head, L1 Origin, and the current mempool.
createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, l1Origin eth.L1BlockRef) (eth.L2BlockRef, *l2.ExecutionPayload, error) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, l1Origin eth.L1BlockRef) (eth.L2BlockRef, *eth.ExecutionPayload, error)
// processBlock simply tries to add the block to the chain, reorging if necessary, and updates the forkchoice of the engine. // processBlock simply tries to add the block to the chain, reorging if necessary, and updates the forkchoice of the engine.
processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, payload *l2.ExecutionPayload) error processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, payload *eth.ExecutionPayload) error
} }
type Network interface { type Network interface {
// PublishL2Payload is called by the driver whenever there is a new payload to publish, synchronously with the driver main loop. // PublishL2Payload is called by the driver whenever there is a new payload to publish, synchronously with the driver main loop.
PublishL2Payload(ctx context.Context, payload *l2.ExecutionPayload) error PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error
} }
func NewDriver(cfg rollup.Config, l2 *l2.Source, l1 *l1.Source, network Network, log log.Logger, snapshotLog log.Logger, sequencer bool) *Driver { func NewDriver(cfg rollup.Config, l2 *l2.Source, l1 *l1.Source, network Network, log log.Logger, snapshotLog log.Logger, sequencer bool) *Driver {
...@@ -82,7 +82,7 @@ func (d *Driver) OnL1Head(ctx context.Context, head eth.L1BlockRef) error { ...@@ -82,7 +82,7 @@ func (d *Driver) OnL1Head(ctx context.Context, head eth.L1BlockRef) error {
return d.s.OnL1Head(ctx, head) return d.s.OnL1Head(ctx, head)
} }
func (d *Driver) OnUnsafeL2Payload(ctx context.Context, payload *l2.ExecutionPayload) error { func (d *Driver) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error {
return d.s.OnUnsafeL2Payload(ctx, payload) return d.s.OnUnsafeL2Payload(ctx, payload)
} }
......
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -28,7 +28,7 @@ type state struct { ...@@ -28,7 +28,7 @@ type state struct {
// Connections (in/out) // Connections (in/out)
l1Heads chan eth.L1BlockRef l1Heads chan eth.L1BlockRef
unsafeL2Payloads chan *l2.ExecutionPayload unsafeL2Payloads chan *eth.ExecutionPayload
l1 L1Chain l1 L1Chain
l2 L2Chain l2 L2Chain
output outputInterface output outputInterface
...@@ -55,7 +55,7 @@ func NewState(log log.Logger, snapshotLog log.Logger, config rollup.Config, l1Ch ...@@ -55,7 +55,7 @@ func NewState(log log.Logger, snapshotLog log.Logger, config rollup.Config, l1Ch
network: network, network: network,
sequencer: sequencer, sequencer: sequencer,
l1Heads: make(chan eth.L1BlockRef, 10), l1Heads: make(chan eth.L1BlockRef, 10),
unsafeL2Payloads: make(chan *l2.ExecutionPayload, 10), unsafeL2Payloads: make(chan *eth.ExecutionPayload, 10),
} }
} }
...@@ -120,7 +120,7 @@ func (s *state) OnL1Head(ctx context.Context, head eth.L1BlockRef) error { ...@@ -120,7 +120,7 @@ func (s *state) OnL1Head(ctx context.Context, head eth.L1BlockRef) error {
} }
} }
func (s *state) OnUnsafeL2Payload(ctx context.Context, payload *l2.ExecutionPayload) error { func (s *state) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
...@@ -167,7 +167,7 @@ func (s *state) handleNewL1Block(ctx context.Context, newL1Head eth.L1BlockRef) ...@@ -167,7 +167,7 @@ func (s *state) handleNewL1Block(ctx context.Context, newL1Head eth.L1BlockRef)
return err return err
} }
// Update forkchoice // Update forkchoice
fc := l2.ForkchoiceState{ fc := eth.ForkchoiceState{
HeadBlockHash: unsafeL2Head.Hash, HeadBlockHash: unsafeL2Head.Hash,
SafeBlockHash: safeL2Head.Hash, SafeBlockHash: safeL2Head.Hash,
FinalizedBlockHash: s.l2Finalized.Hash, FinalizedBlockHash: s.l2Finalized.Hash,
...@@ -316,7 +316,7 @@ func (s *state) handleEpoch(ctx context.Context) (bool, error) { ...@@ -316,7 +316,7 @@ func (s *state) handleEpoch(ctx context.Context) (bool, error) {
} }
func (s *state) handleUnsafeL2Payload(ctx context.Context, payload *l2.ExecutionPayload) error { func (s *state) handleUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error {
if s.l2SafeHead.Number > uint64(payload.BlockNumber) { if s.l2SafeHead.Number > uint64(payload.BlockNumber) {
s.log.Info("ignoring unsafe L2 execution payload, already have safe payload", "id", payload.ID()) s.log.Info("ignoring unsafe L2 execution payload, already have safe payload", "id", payload.ID())
return nil return nil
...@@ -326,7 +326,7 @@ func (s *state) handleUnsafeL2Payload(ctx context.Context, payload *l2.Execution ...@@ -326,7 +326,7 @@ func (s *state) handleUnsafeL2Payload(ctx context.Context, payload *l2.Execution
// The engine should never reorg past the finalized block hash however. // The engine should never reorg past the finalized block hash however.
// The engine may attempt syncing via p2p if there is a larger gap in the L2 chain. // The engine may attempt syncing via p2p if there is a larger gap in the L2 chain.
l2Ref, err := l2.PayloadToBlockRef(payload, &s.Config.Genesis) l2Ref, err := derive.PayloadToBlockRef(payload, &s.Config.Genesis)
if err != nil { if err != nil {
return fmt.Errorf("failed to derive L2 block ref from payload: %v", err) return fmt.Errorf("failed to derive L2 block ref from payload: %v", err)
} }
......
This diff is collapsed.
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
...@@ -28,7 +27,7 @@ type outputImpl struct { ...@@ -28,7 +27,7 @@ type outputImpl struct {
// isDepositTx checks an opaqueTx to determine if it is a Deposit Trransaction // isDepositTx checks an opaqueTx to determine if it is a Deposit Trransaction
// It has to return an error in the case the transaction is empty // It has to return an error in the case the transaction is empty
func isDepositTx(opaqueTx l2.Data) (bool, error) { func isDepositTx(opaqueTx eth.Data) (bool, error) {
if len(opaqueTx) == 0 { if len(opaqueTx) == 0 {
return false, errors.New("empty transaction") return false, errors.New("empty transaction")
} }
...@@ -38,7 +37,7 @@ func isDepositTx(opaqueTx l2.Data) (bool, error) { ...@@ -38,7 +37,7 @@ func isDepositTx(opaqueTx l2.Data) (bool, error) {
// lastDeposit finds the index of last deposit at the start of the transactions. // lastDeposit finds the index of last deposit at the start of the transactions.
// It walks the transactions from the start until it finds a non-deposit tx. // It walks the transactions from the start until it finds a non-deposit tx.
// An error is returned if any looked at transaction cannot be decoded // An error is returned if any looked at transaction cannot be decoded
func lastDeposit(txns []l2.Data) (int, error) { func lastDeposit(txns []eth.Data) (int, error) {
var lastDeposit int var lastDeposit int
for i, tx := range txns { for i, tx := range txns {
deposit, err := isDepositTx(tx) deposit, err := isDepositTx(tx)
...@@ -54,13 +53,13 @@ func lastDeposit(txns []l2.Data) (int, error) { ...@@ -54,13 +53,13 @@ func lastDeposit(txns []l2.Data) (int, error) {
return lastDeposit, nil return lastDeposit, nil
} }
func (d *outputImpl) processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, payload *l2.ExecutionPayload) error { func (d *outputImpl) processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, payload *eth.ExecutionPayload) error {
d.log.Info("processing new block", "parent", payload.ParentID(), "l2Head", l2Head, "id", payload.ID()) d.log.Info("processing new block", "parent", payload.ParentID(), "l2Head", l2Head, "id", payload.ID())
if err := d.l2.NewPayload(ctx, payload); err != nil { if err := d.l2.NewPayload(ctx, payload); err != nil {
return fmt.Errorf("failed to insert new payload: %v", err) return fmt.Errorf("failed to insert new payload: %v", err)
} }
// now try to persist a reorg to the new payload // now try to persist a reorg to the new payload
fc := l2.ForkchoiceState{ fc := eth.ForkchoiceState{
HeadBlockHash: payload.BlockHash, HeadBlockHash: payload.BlockHash,
SafeBlockHash: l2SafeHead.Hash, SafeBlockHash: l2SafeHead.Hash,
FinalizedBlockHash: l2Finalized.Hash, FinalizedBlockHash: l2Finalized.Hash,
...@@ -69,13 +68,13 @@ func (d *outputImpl) processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2 ...@@ -69,13 +68,13 @@ func (d *outputImpl) processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2
if err != nil { if err != nil {
return fmt.Errorf("failed to update forkchoice to point to new payload: %v", err) return fmt.Errorf("failed to update forkchoice to point to new payload: %v", err)
} }
if res.PayloadStatus.Status != l2.ExecutionValid { if res.PayloadStatus.Status != eth.ExecutionValid {
return fmt.Errorf("failed to persist forkchoice update: %v", err) return fmt.Errorf("failed to persist forkchoice update: %v", err)
} }
return nil return nil
} }
func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, l1Origin eth.L1BlockRef) (eth.L2BlockRef, *l2.ExecutionPayload, error) { func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, l1Origin eth.L1BlockRef) (eth.L2BlockRef, *eth.ExecutionPayload, error) {
d.log.Info("creating new block", "parent", l2Head, "l1Origin", l1Origin) d.log.Info("creating new block", "parent", l2Head, "l1Origin", l1Origin)
fetchCtx, cancel := context.WithTimeout(ctx, time.Second*20) fetchCtx, cancel := context.WithTimeout(ctx, time.Second*20)
...@@ -101,7 +100,7 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, ...@@ -101,7 +100,7 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef,
} }
// Start building the list of transactions to include in the new block. // Start building the list of transactions to include in the new block.
var txns []l2.Data var txns []eth.Data
// First transaction in every block is always the L1 info transaction. // First transaction in every block is always the L1 info transaction.
l1InfoTx, err := derive.L1InfoDepositBytes(seqNumber, l1Info) l1InfoTx, err := derive.L1InfoDepositBytes(seqNumber, l1Info)
...@@ -128,9 +127,9 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, ...@@ -128,9 +127,9 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef,
shouldProduceEmptyBlock := nextL2Time >= l1Origin.Time+d.Config.MaxSequencerDrift shouldProduceEmptyBlock := nextL2Time >= l1Origin.Time+d.Config.MaxSequencerDrift
// Put together our payload attributes. // Put together our payload attributes.
attrs := &l2.PayloadAttributes{ attrs := &eth.PayloadAttributes{
Timestamp: hexutil.Uint64(nextL2Time), Timestamp: hexutil.Uint64(nextL2Time),
PrevRandao: l2.Bytes32(l1Info.MixDigest()), PrevRandao: eth.Bytes32(l1Info.MixDigest()),
SuggestedFeeRecipient: d.Config.FeeRecipientAddress, SuggestedFeeRecipient: d.Config.FeeRecipientAddress,
Transactions: txns, Transactions: txns,
NoTxPool: shouldProduceEmptyBlock, NoTxPool: shouldProduceEmptyBlock,
...@@ -138,7 +137,7 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, ...@@ -138,7 +137,7 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef,
// And construct our fork choice state. This is our current fork choice state and will be // And construct our fork choice state. This is our current fork choice state and will be
// updated as a result of executing the block based on the attributes described above. // updated as a result of executing the block based on the attributes described above.
fc := l2.ForkchoiceState{ fc := eth.ForkchoiceState{
HeadBlockHash: l2Head.Hash, HeadBlockHash: l2Head.Hash,
SafeBlockHash: l2SafeHead.Hash, SafeBlockHash: l2SafeHead.Hash,
FinalizedBlockHash: l2Finalized.Hash, FinalizedBlockHash: l2Finalized.Hash,
...@@ -151,7 +150,7 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, ...@@ -151,7 +150,7 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef,
} }
// Generate an L2 block ref from the payload. // Generate an L2 block ref from the payload.
ref, err := l2.PayloadToBlockRef(payload, &d.Config.Genesis) ref, err := derive.PayloadToBlockRef(payload, &d.Config.Genesis)
return ref, payload, err return ref, payload, err
} }
...@@ -215,7 +214,7 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S ...@@ -215,7 +214,7 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S
batches = derive.FilterBatches(&d.Config, epoch, minL2Time, maxL2Time, batches) batches = derive.FilterBatches(&d.Config, epoch, minL2Time, maxL2Time, batches)
batches = derive.FillMissingBatches(batches, uint64(epoch), d.Config.BlockTime, minL2Time, nextL1Block.Time()) batches = derive.FillMissingBatches(batches, uint64(epoch), d.Config.BlockTime, minL2Time, nextL1Block.Time())
fc := l2.ForkchoiceState{ fc := eth.ForkchoiceState{
HeadBlockHash: l2Head.Hash, HeadBlockHash: l2Head.Hash,
SafeBlockHash: l2SafeHead.Hash, SafeBlockHash: l2SafeHead.Hash,
FinalizedBlockHash: l2Finalized.Hash, FinalizedBlockHash: l2Finalized.Hash,
...@@ -224,10 +223,10 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S ...@@ -224,10 +223,10 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S
lastHead := l2Head lastHead := l2Head
lastSafeHead := l2SafeHead lastSafeHead := l2SafeHead
didReorg := false didReorg := false
var payload *l2.ExecutionPayload var payload *eth.ExecutionPayload
var reorg bool var reorg bool
for i, batch := range batches { for i, batch := range batches {
var txns []l2.Data var txns []eth.Data
l1InfoTx, err := derive.L1InfoDepositBytes(uint64(i), l1Info) l1InfoTx, err := derive.L1InfoDepositBytes(uint64(i), l1Info)
if err != nil { if err != nil {
return l2Head, l2SafeHead, false, fmt.Errorf("failed to create l1InfoTx: %w", err) return l2Head, l2SafeHead, false, fmt.Errorf("failed to create l1InfoTx: %w", err)
...@@ -237,9 +236,9 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S ...@@ -237,9 +236,9 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S
txns = append(txns, deposits...) txns = append(txns, deposits...)
} }
txns = append(txns, batch.Transactions...) txns = append(txns, batch.Transactions...)
attrs := &l2.PayloadAttributes{ attrs := &eth.PayloadAttributes{
Timestamp: hexutil.Uint64(batch.Timestamp), Timestamp: hexutil.Uint64(batch.Timestamp),
PrevRandao: l2.Bytes32(l1Info.MixDigest()), PrevRandao: eth.Bytes32(l1Info.MixDigest()),
SuggestedFeeRecipient: d.Config.FeeRecipientAddress, SuggestedFeeRecipient: d.Config.FeeRecipientAddress,
Transactions: txns, Transactions: txns,
// we are verifying, not sequencing, we've got all transactions and do not pull from the tx-pool // we are verifying, not sequencing, we've got all transactions and do not pull from the tx-pool
...@@ -264,7 +263,7 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S ...@@ -264,7 +263,7 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S
return lastHead, lastSafeHead, didReorg, fmt.Errorf("failed to extend L2 chain at block %d/%d of epoch %d: %w", i, len(batches), epoch, err) return lastHead, lastSafeHead, didReorg, fmt.Errorf("failed to extend L2 chain at block %d/%d of epoch %d: %w", i, len(batches), epoch, err)
} }
newLast, err := l2.PayloadToBlockRef(payload, &d.Config.Genesis) newLast, err := derive.PayloadToBlockRef(payload, &d.Config.Genesis)
if err != nil { if err != nil {
return lastHead, lastSafeHead, didReorg, fmt.Errorf("failed to derive block references: %w", err) return lastHead, lastSafeHead, didReorg, fmt.Errorf("failed to derive block references: %w", err)
} }
...@@ -286,7 +285,7 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S ...@@ -286,7 +285,7 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S
// attributesMatchBlock checks if the L2 attributes pre-inputs match the output // attributesMatchBlock checks if the L2 attributes pre-inputs match the output
// nil if it is a match. If err is not nil, the error contains the reason for the mismatch // nil if it is a match. If err is not nil, the error contains the reason for the mismatch
func attributesMatchBlock(attrs *l2.PayloadAttributes, parentHash common.Hash, block *l2.ExecutionPayload) error { func attributesMatchBlock(attrs *eth.PayloadAttributes, parentHash common.Hash, block *eth.ExecutionPayload) error {
if parentHash != block.ParentHash { if parentHash != block.ParentHash {
return fmt.Errorf("parent hash field does not match. expected: %v. got: %v", parentHash, block.ParentHash) return fmt.Errorf("parent hash field does not match. expected: %v. got: %v", parentHash, block.ParentHash)
} }
...@@ -309,12 +308,12 @@ func attributesMatchBlock(attrs *l2.PayloadAttributes, parentHash common.Hash, b ...@@ -309,12 +308,12 @@ func attributesMatchBlock(attrs *l2.PayloadAttributes, parentHash common.Hash, b
// verifySafeBlock reconciles the supplied payload attributes against the actual L2 block. // verifySafeBlock reconciles the supplied payload attributes against the actual L2 block.
// If they do not match, it inserts the new block and sets the head and safe head to the new block in the FC. // If they do not match, it inserts the new block and sets the head and safe head to the new block in the FC.
func (d *outputImpl) verifySafeBlock(ctx context.Context, fc l2.ForkchoiceState, attrs *l2.PayloadAttributes, parent eth.BlockID) (*l2.ExecutionPayload, bool, error) { func (d *outputImpl) verifySafeBlock(ctx context.Context, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes, parent eth.BlockID) (*eth.ExecutionPayload, bool, error) {
payload, err := d.l2.PayloadByNumber(ctx, new(big.Int).SetUint64(parent.Number+1)) payload, err := d.l2.PayloadByNumber(ctx, new(big.Int).SetUint64(parent.Number+1))
if err != nil { if err != nil {
return nil, false, fmt.Errorf("failed to get L2 block: %w", err) return nil, false, fmt.Errorf("failed to get L2 block: %w", err)
} }
ref, err := l2.PayloadToBlockRef(payload, &d.Config.Genesis) ref, err := derive.PayloadToBlockRef(payload, &d.Config.Genesis)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("failed to parse block ref: %w", err) return nil, false, fmt.Errorf("failed to parse block ref: %w", err)
} }
...@@ -343,12 +342,12 @@ func (d *outputImpl) verifySafeBlock(ctx context.Context, fc l2.ForkchoiceState, ...@@ -343,12 +342,12 @@ func (d *outputImpl) verifySafeBlock(ctx context.Context, fc l2.ForkchoiceState,
// sets the FC to the same safe and finalized hashes, but updates the head hash to the new block. // sets the FC to the same safe and finalized hashes, but updates the head hash to the new block.
// If updateSafe is true, the head block is considered to be the safe head as well as the head. // If updateSafe is true, the head block is considered to be the safe head as well as the head.
// It returns the payload, the count of deposits, and an error. // It returns the payload, the count of deposits, and an error.
func (d *outputImpl) insertHeadBlock(ctx context.Context, fc l2.ForkchoiceState, attrs *l2.PayloadAttributes, updateSafe bool) (*l2.ExecutionPayload, error) { func (d *outputImpl) insertHeadBlock(ctx context.Context, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes, updateSafe bool) (*eth.ExecutionPayload, error) {
fcRes, err := d.l2.ForkchoiceUpdate(ctx, &fc, attrs) fcRes, err := d.l2.ForkchoiceUpdate(ctx, &fc, attrs)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create new block via forkchoice: %w", err) return nil, fmt.Errorf("failed to create new block via forkchoice: %w", err)
} }
if fcRes.PayloadStatus.Status != l2.ExecutionValid { if fcRes.PayloadStatus.Status != eth.ExecutionValid {
return nil, fmt.Errorf("engine not ready, forkchoice pre-state is not valid: %s", fcRes.PayloadStatus.Status) return nil, fmt.Errorf("engine not ready, forkchoice pre-state is not valid: %s", fcRes.PayloadStatus.Status)
} }
id := fcRes.PayloadID id := fcRes.PayloadID
...@@ -403,7 +402,7 @@ func (d *outputImpl) insertHeadBlock(ctx context.Context, fc l2.ForkchoiceState, ...@@ -403,7 +402,7 @@ func (d *outputImpl) insertHeadBlock(ctx context.Context, fc l2.ForkchoiceState,
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to make the new L2 block canonical via forkchoice: %w", err) return nil, fmt.Errorf("failed to make the new L2 block canonical via forkchoice: %w", err)
} }
if fcRes.PayloadStatus.Status != l2.ExecutionValid { if fcRes.PayloadStatus.Status != eth.ExecutionValid {
return nil, fmt.Errorf("failed to persist forkchoice change: %s", fcRes.PayloadStatus.Status) return nil, fmt.Errorf("failed to persist forkchoice change: %s", fcRes.PayloadStatus.Status)
} }
return payload, nil return payload, nil
......
package testutils
import (
"strconv"
"strings"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
)
// TestID represents an eth.BlockID as string, and can be shortened for convenience in test definitions.
//
// Format: <hash-characters>:<number> where the <hash-characters> are
// copied over (i.e. not hex) and <number> is in decimal.
//
// Examples: "foobar:123", or "B:2"
type TestID string
func (id TestID) ID() eth.BlockID {
parts := strings.Split(string(id), ":")
if len(parts) != 2 {
panic("bad id")
}
if len(parts[0]) > 32 {
panic("test ID hash too long")
}
var h common.Hash
copy(h[:], parts[0])
v, err := strconv.ParseUint(parts[1], 0, 64)
if err != nil {
panic(err)
}
return eth.BlockID{
Hash: h,
Number: v,
}
}
package testutils
import (
"math/big"
"math/rand"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// Returns a DepositEvent customized on the basis of the id parameter.
func GenerateDeposit(sourceHash common.Hash, rng *rand.Rand) *types.DepositTx {
dataLen := rng.Int63n(10_000)
data := make([]byte, dataLen)
rng.Read(data)
var to *common.Address
if rng.Intn(2) == 0 {
x := RandomAddress(rng)
to = &x
}
var mint *big.Int
if rng.Intn(2) == 0 {
mint = RandomETH(rng, 200)
}
dep := &types.DepositTx{
SourceHash: sourceHash,
From: RandomAddress(rng),
To: to,
Value: RandomETH(rng, 200),
Gas: uint64(rng.Int63n(10 * 1e6)), // 10 M gas max
Data: data,
Mint: mint,
}
return dep
}
// Generates an EVM log entry with the given topics and data.
func GenerateLog(addr common.Address, topics []common.Hash, data []byte) *types.Log {
return &types.Log{
Address: addr,
Topics: topics,
Data: data,
Removed: false,
// ignored (zeroed):
BlockNumber: 0,
TxHash: common.Hash{},
TxIndex: 0,
BlockHash: common.Hash{},
Index: 0,
}
}
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
) )
...@@ -167,7 +166,7 @@ func (m *FakeChainSource) L2BlockRefByHash(ctx context.Context, l2Hash common.Ha ...@@ -167,7 +166,7 @@ func (m *FakeChainSource) L2BlockRefByHash(ctx context.Context, l2Hash common.Ha
return eth.L2BlockRef{}, ethereum.NotFound return eth.L2BlockRef{}, ethereum.NotFound
} }
func (m *FakeChainSource) ForkchoiceUpdate(ctx context.Context, state *l2.ForkchoiceState, attr *l2.PayloadAttributes) (*l2.ForkchoiceUpdatedResult, error) { func (m *FakeChainSource) ForkchoiceUpdate(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
m.log.Trace("ForkchoiceUpdate", "newHead", state.HeadBlockHash, "l2Head", m.l2head, "reorg", m.l2reorg) m.log.Trace("ForkchoiceUpdate", "newHead", state.HeadBlockHash, "l2Head", m.l2head, "reorg", m.l2reorg)
m.l2reorg++ m.l2reorg++
if m.l2reorg >= len(m.l2s) { if m.l2reorg >= len(m.l2s) {
......
package testutils
import (
"math/big"
"math/rand"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type MockL1Info struct {
// Prefixed all fields with "Info" to avoid collisions with the interface method names.
InfoHash common.Hash
InfoParentHash common.Hash
InfoRoot common.Hash
InfoNum uint64
InfoTime uint64
InfoMixDigest [32]byte
InfoBaseFee *big.Int
InfoReceiptRoot common.Hash
InfoSequenceNumber uint64
}
func (l *MockL1Info) Hash() common.Hash {
return l.InfoHash
}
func (l *MockL1Info) ParentHash() common.Hash {
return l.InfoParentHash
}
func (l *MockL1Info) Root() common.Hash {
return l.InfoRoot
}
func (l *MockL1Info) NumberU64() uint64 {
return l.InfoNum
}
func (l *MockL1Info) Time() uint64 {
return l.InfoTime
}
func (l *MockL1Info) MixDigest() common.Hash {
return l.InfoMixDigest
}
func (l *MockL1Info) BaseFee() *big.Int {
return l.InfoBaseFee
}
func (l *MockL1Info) ReceiptHash() common.Hash {
return l.InfoReceiptRoot
}
func (l *MockL1Info) ID() eth.BlockID {
return eth.BlockID{Hash: l.InfoHash, Number: l.InfoNum}
}
func (l *MockL1Info) BlockRef() eth.L1BlockRef {
return eth.L1BlockRef{
Hash: l.InfoHash,
Number: l.InfoNum,
ParentHash: l.InfoParentHash,
Time: l.InfoTime,
}
}
func (l *MockL1Info) SequenceNumber() uint64 {
return l.InfoSequenceNumber
}
func RandomL1Info(rng *rand.Rand) *MockL1Info {
return &MockL1Info{
InfoParentHash: RandomHash(rng),
InfoNum: rng.Uint64(),
InfoTime: rng.Uint64(),
InfoHash: RandomHash(rng),
InfoBaseFee: big.NewInt(rng.Int63n(1000_000 * 1e9)), // a million GWEI
InfoReceiptRoot: types.EmptyRootHash,
InfoRoot: RandomHash(rng),
InfoSequenceNumber: rng.Uint64(),
}
}
func MakeL1Info(fn func(l *MockL1Info)) func(rng *rand.Rand) *MockL1Info {
return func(rng *rand.Rand) *MockL1Info {
l := RandomL1Info(rng)
if fn != nil {
fn(l)
}
return l
}
}
package testutils
import (
"math/big"
"math/rand"
"github.com/ethereum/go-ethereum/common"
)
func RandomHash(rng *rand.Rand) (out common.Hash) {
rng.Read(out[:])
return
}
func RandomAddress(rng *rand.Rand) (out common.Address) {
rng.Read(out[:])
return
}
func RandomETH(rng *rand.Rand, max int64) *big.Int {
x := big.NewInt(rng.Int63n(max))
x = new(big.Int).Mul(x, big.NewInt(1e18))
return x
}
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"strings" "strings"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/l2" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-proposer/rollupclient" "github.com/ethereum-optimism/optimism/op-proposer/rollupclient"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
) )
var bigOne = big.NewInt(1) var bigOne = big.NewInt(1)
var supportedL2OutputVersion = l2.Bytes32{} var supportedL2OutputVersion = eth.Bytes32{}
type Config struct { type Config struct {
Log log.Logger Log log.Logger
...@@ -261,16 +261,16 @@ func (d *Driver) SendTransaction( ...@@ -261,16 +261,16 @@ func (d *Driver) SendTransaction(
return d.cfg.L1Client.SendTransaction(ctx, tx) return d.cfg.L1Client.SendTransaction(ctx, tx)
} }
func (d *Driver) outputRootAtBlock(ctx context.Context, blockNum *big.Int) (l2.Bytes32, error) { func (d *Driver) outputRootAtBlock(ctx context.Context, blockNum *big.Int) (eth.Bytes32, error) {
output, err := d.cfg.RollupClient.OutputAtBlock(ctx, blockNum) output, err := d.cfg.RollupClient.OutputAtBlock(ctx, blockNum)
if err != nil { if err != nil {
return l2.Bytes32{}, err return eth.Bytes32{}, err
} }
if len(output) != 2 { if len(output) != 2 {
return l2.Bytes32{}, fmt.Errorf("invalid outputAtBlock response") return eth.Bytes32{}, fmt.Errorf("invalid outputAtBlock response")
} }
if version := output[0]; version != supportedL2OutputVersion { if version := output[0]; version != supportedL2OutputVersion {
return l2.Bytes32{}, fmt.Errorf("unsupported l2 output version") return eth.Bytes32{}, fmt.Errorf("unsupported l2 output version")
} }
return output[1], nil return output[1], nil
} }
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"context" "context"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-node/l2" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/node" "github.com/ethereum-optimism/optimism/op-node/node"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
...@@ -28,8 +28,8 @@ func (r *RollupClient) GetBatchBundle( ...@@ -28,8 +28,8 @@ func (r *RollupClient) GetBatchBundle(
return batchResponse, err return batchResponse, err
} }
func (r *RollupClient) OutputAtBlock(ctx context.Context, blockNum *big.Int) ([]l2.Bytes32, error) { func (r *RollupClient) OutputAtBlock(ctx context.Context, blockNum *big.Int) ([]eth.Bytes32, error) {
var output []l2.Bytes32 var output []eth.Bytes32
err := r.rpc.CallContext(ctx, &output, "optimism_outputAtBlock", hexutil.EncodeBig(blockNum)) err := r.rpc.CallContext(ctx, &output, "optimism_outputAtBlock", hexutil.EncodeBig(blockNum))
return output, err return output, err
} }
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