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)
} }
......
...@@ -2,17 +2,13 @@ package driver ...@@ -2,17 +2,13 @@ package driver
import ( import (
"context" "context"
"strconv"
"strings"
"testing" "testing"
"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/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -21,31 +17,11 @@ import ( ...@@ -21,31 +17,11 @@ import (
var _ L1Chain = (*testutils.FakeChainSource)(nil) var _ L1Chain = (*testutils.FakeChainSource)(nil)
var _ L2Chain = (*testutils.FakeChainSource)(nil) var _ L2Chain = (*testutils.FakeChainSource)(nil)
type testID string type TestID = testutils.TestID
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,
}
}
type outputHandlerFn func(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.L2BlockRef, l2Finalized eth.BlockID, l1Input []eth.BlockID) (eth.L2BlockRef, eth.L2BlockRef, bool, error) type outputHandlerFn func(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.L2BlockRef, l2Finalized eth.BlockID, l1Input []eth.BlockID) (eth.L2BlockRef, eth.L2BlockRef, bool, error)
func (fn outputHandlerFn) processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, payload *l2.ExecutionPayload) error { func (fn outputHandlerFn) processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, payload *eth.ExecutionPayload) error {
// TODO: maybe mock a failed block? // TODO: maybe mock a failed block?
return nil return nil
} }
...@@ -54,7 +30,7 @@ func (fn outputHandlerFn) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef ...@@ -54,7 +30,7 @@ func (fn outputHandlerFn) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef
return fn(ctx, l2Head, l2SafeHead, l2Finalized, l1Input) return fn(ctx, l2Head, l2SafeHead, l2Finalized, l1Input)
} }
func (fn outputHandlerFn) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, l1Origin eth.L1BlockRef) (eth.L2BlockRef, *l2.ExecutionPayload, error) { func (fn outputHandlerFn) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, l1Origin eth.L1BlockRef) (eth.L2BlockRef, *eth.ExecutionPayload, error) {
panic("Unimplemented") panic("Unimplemented")
} }
...@@ -71,13 +47,13 @@ type outputReturnArgs struct { ...@@ -71,13 +47,13 @@ type outputReturnArgs struct {
type stateTestCaseStep struct { type stateTestCaseStep struct {
// Expect l1head, l2head, and sequence window // Expect l1head, l2head, and sequence window
l1head testID l1head TestID
l2head testID l2head TestID
window []testID window []TestID
// l1act and l2act are ran at each step // l1act and l2act are ran at each step
l1act func(t *testing.T, s *state, src *testutils.FakeChainSource) l1act func(t *testing.T, s *state, src *testutils.FakeChainSource)
l2act func(t *testing.T, expectedWindow []testID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs) l2act func(t *testing.T, expectedWindow []TestID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs)
reorg bool reorg bool
} }
...@@ -99,7 +75,7 @@ func stutterAdvance(t *testing.T, s *state, src *testutils.FakeChainSource) { ...@@ -99,7 +75,7 @@ func stutterAdvance(t *testing.T, s *state, src *testutils.FakeChainSource) {
stutterL1(t, s, src) stutterL1(t, s, src)
} }
func stutterL2(t *testing.T, expectedWindow []testID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs) { func stutterL2(t *testing.T, expectedWindow []TestID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs) {
select { select {
case <-outputIn: case <-outputIn:
t.Error("Got a step when no step should have occurred (l1 only advance)") t.Error("Got a step when no step should have occurred (l1 only advance)")
...@@ -107,7 +83,7 @@ func stutterL2(t *testing.T, expectedWindow []testID, s *state, src *testutils.F ...@@ -107,7 +83,7 @@ func stutterL2(t *testing.T, expectedWindow []testID, s *state, src *testutils.F
} }
} }
func advanceL2(t *testing.T, expectedWindow []testID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs) { func advanceL2(t *testing.T, expectedWindow []TestID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs) {
args := <-outputIn args := <-outputIn
assert.Equal(t, int(s.Config.SeqWindowSize), len(args.l1Window), "Invalid L1 window size") assert.Equal(t, int(s.Config.SeqWindowSize), len(args.l1Window), "Invalid L1 window size")
assert.Equal(t, len(expectedWindow), len(args.l1Window), "L1 Window size does not match expectedWindow") assert.Equal(t, len(expectedWindow), len(args.l1Window), "L1 Window size does not match expectedWindow")
...@@ -117,7 +93,7 @@ func advanceL2(t *testing.T, expectedWindow []testID, s *state, src *testutils.F ...@@ -117,7 +93,7 @@ func advanceL2(t *testing.T, expectedWindow []testID, s *state, src *testutils.F
outputReturn <- outputReturnArgs{l2Head: src.SetL2Head(int(args.l2Head.Number) + 1), err: nil} outputReturn <- outputReturnArgs{l2Head: src.SetL2Head(int(args.l2Head.Number) + 1), err: nil}
} }
func reorg__L2(t *testing.T, expectedWindow []testID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs) { func reorg__L2(t *testing.T, expectedWindow []TestID, s *state, src *testutils.FakeChainSource, outputIn chan outputArgs, outputReturn chan outputReturnArgs) {
args := <-outputIn args := <-outputIn
assert.Equal(t, int(s.Config.SeqWindowSize), len(args.l1Window), "Invalid L1 window size") assert.Equal(t, int(s.Config.SeqWindowSize), len(args.l1Window), "Invalid L1 window size")
assert.Equal(t, len(expectedWindow), len(args.l1Window), "L1 Window size does not match expectedWindow") assert.Equal(t, len(expectedWindow), len(args.l1Window), "L1 Window size does not match expectedWindow")
...@@ -183,12 +159,12 @@ func TestDriver(t *testing.T) { ...@@ -183,12 +159,12 @@ func TestDriver(t *testing.T) {
genesis: testutils.FakeGenesis('a', 'A', 0), genesis: testutils.FakeGenesis('a', 'A', 0),
steps: []stateTestCaseStep{ steps: []stateTestCaseStep{
{l1act: stutterL1, l2act: stutterL2, l1head: "a:0", l2head: "A:0"}, {l1act: stutterL1, l2act: stutterL2, l1head: "a:0", l2head: "A:0"},
{l1act: advanceL1, l2act: stutterL2, l1head: "b:1", l2head: "A:0", window: []testID{"a:0", "b:1"}}, {l1act: advanceL1, l2act: stutterL2, l1head: "b:1", l2head: "A:0", window: []TestID{"a:0", "b:1"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "c:2", l2head: "B:1", window: []testID{"b:1", "c:2"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "c:2", l2head: "B:1", window: []TestID{"b:1", "c:2"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "d:3", l2head: "C:2", window: []testID{"c:2", "d:3"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "d:3", l2head: "C:2", window: []TestID{"c:2", "d:3"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "e:4", l2head: "D:3", window: []testID{"d:3", "e:4"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "e:4", l2head: "D:3", window: []TestID{"d:3", "e:4"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "f:5", l2head: "E:4", window: []testID{"e:4", "f:5"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "f:5", l2head: "E:4", window: []TestID{"e:4", "f:5"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "g:6", l2head: "F:5", window: []testID{"f:5", "g:6"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "g:6", l2head: "F:5", window: []TestID{"f:5", "g:6"}},
}, },
}, },
{ {
...@@ -199,17 +175,17 @@ func TestDriver(t *testing.T) { ...@@ -199,17 +175,17 @@ func TestDriver(t *testing.T) {
genesis: testutils.FakeGenesis('a', 'A', 0), genesis: testutils.FakeGenesis('a', 'A', 0),
steps: []stateTestCaseStep{ steps: []stateTestCaseStep{
{l1act: stutterL1, l2act: stutterL2, l1head: "a:0", l2head: "A:0"}, {l1act: stutterL1, l2act: stutterL2, l1head: "a:0", l2head: "A:0"},
{l1act: advanceL1, l2act: stutterL2, l1head: "b:1", l2head: "A:0", window: []testID{"a:0", "b:1"}}, {l1act: advanceL1, l2act: stutterL2, l1head: "b:1", l2head: "A:0", window: []TestID{"a:0", "b:1"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "c:2", l2head: "B:1", window: []testID{"b:1", "c:2"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "c:2", l2head: "B:1", window: []TestID{"b:1", "c:2"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "d:3", l2head: "C:2", window: []testID{"c:2", "d:3"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "d:3", l2head: "C:2", window: []TestID{"c:2", "d:3"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "e:4", l2head: "D:3", window: []testID{"d:3", "e:4"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "e:4", l2head: "D:3", window: []TestID{"d:3", "e:4"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "f:5", l2head: "E:4", window: []testID{"e:4", "f:5"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "f:5", l2head: "E:4", window: []TestID{"e:4", "f:5"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "g:6", l2head: "F:5", window: []testID{"f:5", "g:6"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "g:6", l2head: "F:5", window: []TestID{"f:5", "g:6"}},
{l1act: stutterL1, l2act: reorg__L2, l1head: "z:6", l2head: "C:2", window: []testID{"c:2", "w:3"}, reorg: true}, {l1act: stutterL1, l2act: reorg__L2, l1head: "z:6", l2head: "C:2", window: []TestID{"c:2", "w:3"}, reorg: true},
{l1act: stutterL1, l2act: advanceL2, l1head: "z:6", l2head: "W:3", window: []testID{"w:3", "x:4"}}, {l1act: stutterL1, l2act: advanceL2, l1head: "z:6", l2head: "W:3", window: []TestID{"w:3", "x:4"}},
{l1act: stutterL1, l2act: advanceL2, l1head: "z:6", l2head: "X:4", window: []testID{"x:4", "y:5"}}, {l1act: stutterL1, l2act: advanceL2, l1head: "z:6", l2head: "X:4", window: []TestID{"x:4", "y:5"}},
{l1act: stutterL1, l2act: advanceL2, l1head: "z:6", l2head: "Y:5", window: []testID{"y:5", "z:6"}}, {l1act: stutterL1, l2act: advanceL2, l1head: "z:6", l2head: "Y:5", window: []TestID{"y:5", "z:6"}},
{l1act: stutterL1, l2act: stutterL2, l1head: "z:6", l2head: "Y:5", window: []testID{}}, {l1act: stutterL1, l2act: stutterL2, l1head: "z:6", l2head: "Y:5", window: []TestID{}},
}, },
}, },
{ {
...@@ -220,12 +196,12 @@ func TestDriver(t *testing.T) { ...@@ -220,12 +196,12 @@ func TestDriver(t *testing.T) {
genesis: testutils.FakeGenesis('a', 'A', 0), genesis: testutils.FakeGenesis('a', 'A', 0),
steps: []stateTestCaseStep{ steps: []stateTestCaseStep{
{l1act: stutterL1, l2act: stutterL2, l1head: "a:0", l2head: "A:0"}, {l1act: stutterL1, l2act: stutterL2, l1head: "a:0", l2head: "A:0"},
{l1act: advanceL1, l2act: stutterL2, l1head: "b:1", l2head: "A:0", window: []testID{"a:0", "b:1"}}, {l1act: advanceL1, l2act: stutterL2, l1head: "b:1", l2head: "A:0", window: []TestID{"a:0", "b:1"}},
{l1act: stutterAdvance, l2act: advanceL2, l1head: "c:2", l2head: "B:1", window: []testID{"b:1", "c:2"}}, {l1act: stutterAdvance, l2act: advanceL2, l1head: "c:2", l2head: "B:1", window: []TestID{"b:1", "c:2"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "d:3", l2head: "C:2", window: []testID{"c:2", "d:3"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "d:3", l2head: "C:2", window: []TestID{"c:2", "d:3"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "e:4", l2head: "D:3", window: []testID{"d:3", "e:4"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "e:4", l2head: "D:3", window: []TestID{"d:3", "e:4"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "f:5", l2head: "E:4", window: []testID{"e:4", "f:5"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "f:5", l2head: "E:4", window: []TestID{"e:4", "f:5"}},
{l1act: advanceL1, l2act: advanceL2, l1head: "g:6", l2head: "F:5", window: []testID{"f:5", "g:6"}}, {l1act: advanceL1, l2act: advanceL2, l1head: "g:6", l2head: "F:5", window: []TestID{"f:5", "g:6"}},
}, },
}, },
} }
......
...@@ -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