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 (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"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"
rollupNode "github.com/ethereum-optimism/optimism/op-node/node"
"github.com/ethereum-optimism/optimism/op-node/rollup"
......@@ -20,7 +20,6 @@ import (
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-proposer/rollupclient"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
......@@ -265,7 +264,7 @@ func TestSystemE2E(t *testing.T) {
receipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.L1BlockTime)*time.Second)
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")
tx = types.NewTx(reconstructedDep)
receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second)
......@@ -355,7 +354,7 @@ func TestMintOnRevertedDeposit(t *testing.T) {
receipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.L1BlockTime)*time.Second)
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")
tx = types.NewTx(reconstructedDep)
receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second)
......@@ -501,10 +500,10 @@ func TestSystemMockP2P(t *testing.T) {
var published, received []common.Hash
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)
}
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)
}
cfg.Nodes["sequencer"].Tracer = seqTracer
......@@ -715,7 +714,7 @@ func TestWithdrawals(t *testing.T) {
require.Nil(t, err, "binding withdrawer on L2")
// 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")
tx = types.NewTx(reconstructedDep)
receipt, err = waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.L1BlockTime)*time.Second)
......
......@@ -4,15 +4,14 @@ import (
"context"
"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/libp2p/go-libp2p-core/peer"
)
type FnTracer struct {
OnNewL1HeadFn func(ctx context.Context, sig eth.L1BlockRef)
OnUnsafeL2PayloadFn func(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload)
OnPublishL2PayloadFn func(ctx context.Context, payload *l2.ExecutionPayload)
OnUnsafeL2PayloadFn func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload)
OnPublishL2PayloadFn func(ctx context.Context, payload *eth.ExecutionPayload)
}
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 {
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 {
n.OnPublishL2PayloadFn(ctx, payload)
}
......
package l2
package eth
import (
"encoding/binary"
......
// Package l2 connects to the L2 execution engine over the Engine API.
package l2
package eth
import (
"bytes"
......@@ -9,8 +8,6 @@ import (
"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/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
......@@ -111,16 +108,16 @@ type ExecutionPayload struct {
Transactions []Data `json:"transactions"`
}
func (payload *ExecutionPayload) ID() eth.BlockID {
return eth.BlockID{Hash: payload.BlockHash, Number: uint64(payload.BlockNumber)}
func (payload *ExecutionPayload) ID() BlockID {
return BlockID{Hash: payload.BlockHash, Number: uint64(payload.BlockNumber)}
}
func (payload *ExecutionPayload) ParentID() eth.BlockID {
func (payload *ExecutionPayload) ParentID() BlockID {
n := uint64(payload.BlockNumber)
if n > 0 {
n -= 1
}
return eth.BlockID{Hash: payload.ParentHash, Number: n}
return BlockID{Hash: payload.ParentHash, Number: n}
}
type rawTransactions []Data
......
......@@ -38,26 +38,26 @@ func (s *Source) 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.
block, err := s.client.BlockByHash(ctx, hash)
if err != nil {
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 {
return nil, fmt.Errorf("failed to read L2 block as payload: %w", err)
}
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.
block, err := s.client.BlockByNumber(ctx, number)
if err != nil {
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 {
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
// 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.
// 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.Debug("Sharing forkchoice-updated signal")
fcCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
var result ForkchoiceUpdatedResult
var result eth.ForkchoiceUpdatedResult
err := s.rpc.CallContext(fcCtx, &result, "engine_forkchoiceUpdatedV1", fc, attributes)
if err == nil {
e.Debug("Shared forkchoice-updated signal")
......@@ -82,21 +82,21 @@ func (s *Source) ForkchoiceUpdate(ctx context.Context, fc *ForkchoiceState, attr
} else {
e = e.New("err", err)
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)
} else {
e.Error("Failed to share forkchoice-updated signal")
}
}
switch result.PayloadStatus.Status {
case ExecutionSyncing:
case eth.ExecutionSyncing:
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
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)
case ExecutionValid:
case eth.ExecutionValid:
return &result, nil
default:
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
}
// 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.Debug("sending payload for execution")
execCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
var result PayloadStatusV1
var result eth.PayloadStatusV1
err := s.rpc.CallContext(execCtx, &result, "engine_newPayloadV1", payload)
e.Debug("Received payload execution result", "status", result.Status, "latestValidHash", result.LatestValidHash, "message", result.ValidationError)
if err != nil {
......@@ -119,17 +119,17 @@ func (s *Source) NewPayload(ctx context.Context, payload *ExecutionPayload) erro
}
switch result.Status {
case ExecutionValid:
case eth.ExecutionValid:
return nil
case ExecutionSyncing:
case eth.ExecutionSyncing:
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)
case ExecutionInvalidBlockHash:
case eth.ExecutionInvalidBlockHash:
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)
case ExecutionAccepted:
case eth.ExecutionAccepted:
return fmt.Errorf("execution payload cannot be validated yet, latest valid hash is %s", result.LatestValidHash)
default:
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
}
// 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.Debug("getting payload")
var result ExecutionPayload
var result eth.ExecutionPayload
err := s.rpc.CallContext(ctx, &result, "engine_getPayloadV1", payloadId)
if err != nil {
e = e.New("payload_id", payloadId, "err", err)
if rpcErr, ok := err.(rpc.Error); ok {
code := ErrorCode(rpcErr.ErrorCode())
if code != UnavailablePayload {
code := eth.ErrorCode(rpcErr.ErrorCode())
if code != eth.UnavailablePayload {
e.Warn("unexpected error code in get-payload response", "code", code)
} else {
e.Warn("unavailable payload in get-payload request")
......@@ -217,46 +217,6 @@ func blockToBlockRef(block *types.Block, genesis *rollup.Genesis) (eth.L2BlockRe
}, 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 {
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
......
......@@ -6,6 +6,8 @@ import (
"fmt"
"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/derive"
"github.com/ethereum/go-ethereum/core/types"
......@@ -20,13 +22,13 @@ import (
"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
buf.Write(l2OutputRootVersion[:])
buf.Write(blockRoot.Bytes())
buf.Write(storageRoot[:])
buf.Write(blockHash.Bytes())
return Bytes32(crypto.Keccak256Hash(buf.Bytes()))
return eth.Bytes32(crypto.Keccak256Hash(buf.Bytes()))
}
type AccountResult struct {
......
......@@ -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
head, err := n.client.GetBlockHeader(ctx, toBlockNumArg(number))
......@@ -76,10 +76,10 @@ func (n *nodeAPI) OutputAtBlock(ctx context.Context, number rpc.BlockNumber) ([]
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)
return []l2.Bytes32{l2OutputRootVersion, l2OutputRoot}, nil
return []eth.Bytes32{l2OutputRootVersion, l2OutputRoot}, nil
}
func (n *nodeAPI) Version(ctx context.Context) (string, error) {
......
......@@ -4,24 +4,23 @@ import (
"context"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/libp2p/go-libp2p-core/peer"
)
// Tracer configures the OpNode to share events
type Tracer interface {
OnNewL1Head(ctx context.Context, sig eth.L1BlockRef)
OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *l2.ExecutionPayload)
OnPublishL2Payload(ctx context.Context, payload *l2.ExecutionPayload)
OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload)
OnPublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload)
}
type noOpTracer struct{}
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)
......@@ -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)
// publish to p2p, if we are running p2p at all
......@@ -230,7 +230,7 @@ func (n *OpNode) PublishL2Payload(ctx context.Context, payload *l2.ExecutionPayl
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
if n.p2pNode != nil && from == n.p2pNode.Host().ID() {
return nil
......
......@@ -94,7 +94,7 @@ func TestOutputAtBlock(t *testing.T) {
client, err := dialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String(), nil)
assert.NoError(t, err)
var out []l2.Bytes32
var out []eth.Bytes32
err = client.CallContext(context.Background(), &out, "optimism_outputAtBlock", "latest")
assert.NoError(t, err)
assert.Len(t, out, 2)
......
......@@ -9,10 +9,11 @@ import (
"sync"
"time"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
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/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
......@@ -216,7 +217,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config) pubsub.ValidatorEx
signatureBytes, payloadBytes := data[:65], data[65:]
// [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 {
log.Warn("invalid payload", "err", err, "peer", id)
return pubsub.ValidationReject
......@@ -286,7 +287,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config) pubsub.ValidatorEx
}
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 {
......@@ -295,7 +296,7 @@ type GossipTopicInfo interface {
type GossipOut interface {
GossipTopicInfo
PublishL2Payload(ctx context.Context, msg *l2.ExecutionPayload, signer Signer) error
PublishL2Payload(ctx context.Context, msg *eth.ExecutionPayload, signer Signer) error
Close() error
}
......@@ -311,7 +312,7 @@ func (p *publisher) BlocksTopicPeers() []peer.ID {
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)
buf := bytes.NewBuffer((*res)[:0])
defer func() {
......@@ -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 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 {
payload, ok := msg.(*l2.ExecutionPayload)
payload, ok := msg.(*eth.ExecutionPayload)
if !ok {
return fmt.Errorf("expected topic validator to parse and validate data into execution payload, but got %T", msg)
}
......
......@@ -9,7 +9,8 @@ import (
"testing"
"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/testlog"
"github.com/ethereum/go-ethereum/log"
......@@ -76,10 +77,10 @@ func TestP2PSimple(t *testing.T) {
}
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 {
return m.OnUnsafeL2PayloadFn(ctx, from, msg)
}
......
......@@ -2,231 +2,12 @@ package derive
import (
"bytes"
"encoding/binary"
"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/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/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) {
var out []*BatchData
var errs []error
......@@ -300,10 +81,6 @@ func ValidBatch(batch *BatchData, config *rollup.Config, epoch rollup.Epoch, min
return true
}
type L2Info interface {
Time() uint64
}
// 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 {
m := make(map[uint64]*BatchData)
......@@ -337,31 +114,3 @@ func FillMissingBatches(batches []*BatchData, epoch, blockTime, minL2Time, nextL
}
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
import (
"encoding/binary"
"fmt"
"math/big"
"math/rand"
"testing"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/assert"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"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) {
for i := int64(0); i < 100; i++ {
t.Run(fmt.Sprintf("random_deposit_%d", i), func(t *testing.T) {
rng := rand.New(rand.NewSource(1234 + i))
source := UserDepositSource{
L1BlockHash: randomHash(rng),
L1BlockHash: testutils.RandomHash(rng),
LogIndex: uint64(rng.Intn(10000)),
}
depInput := GenerateDeposit(source, rng)
log := GenerateDepositLog(depInput)
depInput := testutils.GenerateDeposit(source.SourceHash(), rng)
log := MarshalDepositLogEvent(MockDepositContractAddr, depInput)
log.TxIndex = uint(rng.Intn(10000))
log.Index = uint(source.LogIndex)
log.BlockHash = source.L1BlockHash
depOutput, err := UnmarshalLogEvent(log)
depOutput, err := UnmarshalDepositLogEvent(log)
if err != nil {
t.Fatal(err)
}
......@@ -170,7 +67,7 @@ func TestDeriveUserDeposits(t *testing.T) {
var receipts []*types.Receipt
var expectedDeposits []*types.DepositTx
logIndex := uint(0)
blockHash := randomHash(rng)
blockHash := testutils.RandomHash(rng)
for txIndex, rData := range testCase.receipts {
var logs []*types.Log
status := types.ReceiptStatusSuccessful
......@@ -181,13 +78,13 @@ func TestDeriveUserDeposits(t *testing.T) {
var ev *types.Log
if isDeposit {
source := UserDepositSource{L1BlockHash: blockHash, LogIndex: uint64(logIndex)}
dep := GenerateDeposit(source, rng)
dep := testutils.GenerateDeposit(source.SourceHash(), rng)
if status == types.ReceiptStatusSuccessful {
expectedDeposits = append(expectedDeposits, dep)
}
ev = GenerateDepositLog(dep)
ev = MarshalDepositLogEvent(MockDepositContractAddr, dep)
} else {
ev = GenerateLog(GenerateAddress(rng), nil, nil)
ev = testutils.GenerateLog(testutils.RandomAddress(rng), nil, nil)
}
ev.TxIndex = uint(txIndex)
ev.Index = logIndex
......@@ -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 @@
// turned back into L1 data.
//
// The flow is data is as follows
// receipts, batches -> l2.PayloadAttributes with `payload_attributes.go`
// l2.PayloadAttributes -> l2.ExecutionPayload with `execution_payload.go`
// L2 block -> Corresponding L1 block info with `l1_block_info.go`
// receipts, batches -> eth.PayloadAttributes, by parsing the L1 data and deriving L2 inputs
// l2.PayloadAttributes -> l2.ExecutionPayload, by running the EVM (using an Execution Engine)
// L2 block -> Corresponding L1 block info, by parsing the first deposited transaction
//
// The Payload Attributes derivation stage is a pure function.
// The Execution Payload derivation stage relies on the L2 execution engine to perform the
// state update.
// The Execution Payload derivation stage relies on the L2 execution engine to perform the state update.
// 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
......@@ -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
// 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
// as ensuring that our custom marshalling matches abigen.
func FuzzUnmarshallLogEvent(f *testing.F) {
......@@ -202,7 +202,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) {
depositEvent.Raw = types.Log{} // Clear out the log
// Verify that is passes our custom unmarshalling logic
dep, err := UnmarshalLogEvent(logs[0])
dep, err := UnmarshalDepositLogEvent(logs[0])
if err != nil {
t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err)
}
......
......@@ -6,9 +6,34 @@ import (
"fmt"
"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/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
type L1BlockInfo struct {
Number uint64
......@@ -70,3 +95,50 @@ func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) {
err := info.UnmarshalBinary(data)
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 (
"math/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type l1MockInfo struct {
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)
var _ L1Info = (*testutils.MockL1Info)(nil)
type infoTest struct {
name string
mkInfo func(rng *rand.Rand) *l1MockInfo
mkInfo func(rng *rand.Rand) *testutils.MockL1Info
}
var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeef00000000")
......@@ -112,35 +23,35 @@ var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdea
func TestParseL1InfoDepositTxData(t *testing.T) {
// Go 1.18 will have native fuzzing for us to use, until then, we cover just the below cases
cases := []infoTest{
{"random", makeInfo(nil)},
{"zero basefee", makeInfo(func(l *l1MockInfo) {
l.baseFee = new(big.Int)
{"random", testutils.MakeL1Info(nil)},
{"zero basefee", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.InfoBaseFee = new(big.Int)
})},
{"zero time", makeInfo(func(l *l1MockInfo) {
l.time = 0
{"zero time", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.InfoTime = 0
})},
{"zero num", makeInfo(func(l *l1MockInfo) {
l.num = 0
{"zero num", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.InfoNum = 0
})},
{"zero seq", makeInfo(func(l *l1MockInfo) {
l.sequenceNumber = 0
{"zero seq", testutils.MakeL1Info(func(l *testutils.MockL1Info) {
l.InfoSequenceNumber = 0
})},
{"all zero", func(rng *rand.Rand) *l1MockInfo {
return &l1MockInfo{baseFee: new(big.Int)}
{"all zero", func(rng *rand.Rand) *testutils.MockL1Info {
return &testutils.MockL1Info{InfoBaseFee: new(big.Int)}
}},
}
for i, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
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)
res, err := L1InfoDepositTxData(depTx.Data)
require.NoError(t, err, "expected valid deposit info")
assert.Equal(t, res.Number, info.num)
assert.Equal(t, res.Time, info.time)
assert.Equal(t, res.Number, info.NumberU64())
assert.Equal(t, res.Time, info.Time())
assert.True(t, res.BaseFee.Sign() >= 0)
assert.Equal(t, res.BaseFee.Bytes(), info.baseFee.Bytes())
assert.Equal(t, res.BlockHash, info.hash)
assert.Equal(t, res.BaseFee.Bytes(), info.BaseFee().Bytes())
assert.Equal(t, res.BlockHash, info.Hash())
})
}
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 {
}
type Engine interface {
GetPayload(ctx context.Context, payloadId l2.PayloadID) (*l2.ExecutionPayload, error)
ForkchoiceUpdate(ctx context.Context, state *l2.ForkchoiceState, attr *l2.PayloadAttributes) (*l2.ForkchoiceUpdatedResult, error)
NewPayload(ctx context.Context, payload *l2.ExecutionPayload) error
PayloadByHash(context.Context, common.Hash) (*l2.ExecutionPayload, error)
PayloadByNumber(context.Context, *big.Int) (*l2.ExecutionPayload, error)
GetPayload(ctx context.Context, payloadId eth.PayloadID) (*eth.ExecutionPayload, error)
ForkchoiceUpdate(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error)
NewPayload(ctx context.Context, payload *eth.ExecutionPayload) error
PayloadByHash(context.Context, common.Hash) (*eth.ExecutionPayload, error)
PayloadByNumber(context.Context, *big.Int) (*eth.ExecutionPayload, error)
}
type L1Chain interface {
......@@ -44,7 +44,7 @@ type L1Chain 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)
L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error)
}
......@@ -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)
// 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(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 {
// 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 {
......@@ -82,7 +82,7 @@ func (d *Driver) OnL1Head(ctx context.Context, head eth.L1BlockRef) error {
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)
}
......
......@@ -8,8 +8,8 @@ import (
"time"
"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/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum/go-ethereum/log"
)
......@@ -28,7 +28,7 @@ type state struct {
// Connections (in/out)
l1Heads chan eth.L1BlockRef
unsafeL2Payloads chan *l2.ExecutionPayload
unsafeL2Payloads chan *eth.ExecutionPayload
l1 L1Chain
l2 L2Chain
output outputInterface
......@@ -55,7 +55,7 @@ func NewState(log log.Logger, snapshotLog log.Logger, config rollup.Config, l1Ch
network: network,
sequencer: sequencer,
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 {
}
}
func (s *state) OnUnsafeL2Payload(ctx context.Context, payload *l2.ExecutionPayload) error {
func (s *state) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error {
select {
case <-ctx.Done():
return ctx.Err()
......@@ -167,7 +167,7 @@ func (s *state) handleNewL1Block(ctx context.Context, newL1Head eth.L1BlockRef)
return err
}
// Update forkchoice
fc := l2.ForkchoiceState{
fc := eth.ForkchoiceState{
HeadBlockHash: unsafeL2Head.Hash,
SafeBlockHash: safeL2Head.Hash,
FinalizedBlockHash: s.l2Finalized.Hash,
......@@ -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) {
s.log.Info("ignoring unsafe L2 execution payload, already have safe payload", "id", payload.ID())
return nil
......@@ -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 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 {
return fmt.Errorf("failed to derive L2 block ref from payload: %v", err)
}
......
......@@ -2,17 +2,13 @@ package driver
import (
"context"
"strconv"
"strings"
"testing"
"time"
"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/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
......@@ -21,31 +17,11 @@ import (
var _ L1Chain = (*testutils.FakeChainSource)(nil)
var _ L2Chain = (*testutils.FakeChainSource)(nil)
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,
}
}
type TestID = testutils.TestID
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?
return nil
}
......@@ -54,7 +30,7 @@ func (fn outputHandlerFn) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef
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")
}
......@@ -71,13 +47,13 @@ type outputReturnArgs struct {
type stateTestCaseStep struct {
// Expect l1head, l2head, and sequence window
l1head testID
l2head testID
window []testID
l1head TestID
l2head TestID
window []TestID
// l1act and l2act are ran at each step
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
}
......@@ -99,7 +75,7 @@ func stutterAdvance(t *testing.T, s *state, src *testutils.FakeChainSource) {
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 {
case <-outputIn:
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
}
}
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
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")
......@@ -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}
}
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
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")
......@@ -183,12 +159,12 @@ func TestDriver(t *testing.T) {
genesis: testutils.FakeGenesis('a', 'A', 0),
steps: []stateTestCaseStep{
{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: 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: "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: "g:6", l2head: "F:5", window: []testID{"f:5", "g:6"}},
{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: "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: "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"}},
},
},
{
......@@ -199,17 +175,17 @@ func TestDriver(t *testing.T) {
genesis: testutils.FakeGenesis('a', 'A', 0),
steps: []stateTestCaseStep{
{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: 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: "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: "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: 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: "Y:5", window: []testID{"y:5", "z:6"}},
{l1act: stutterL1, l2act: stutterL2, l1head: "z:6", l2head: "Y:5", window: []testID{}},
{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: "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: "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: 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: "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: stutterL2, l1head: "z:6", l2head: "Y:5", window: []TestID{}},
},
},
{
......@@ -220,12 +196,12 @@ func TestDriver(t *testing.T) {
genesis: testutils.FakeGenesis('a', 'A', 0),
steps: []stateTestCaseStep{
{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: 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: "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: "g:6", l2head: "F:5", window: []testID{"f:5", "g:6"}},
{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: 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: "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"}},
},
},
}
......
......@@ -9,7 +9,6 @@ import (
"time"
"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/derive"
......@@ -28,7 +27,7 @@ type outputImpl struct {
// 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
func isDepositTx(opaqueTx l2.Data) (bool, error) {
func isDepositTx(opaqueTx eth.Data) (bool, error) {
if len(opaqueTx) == 0 {
return false, errors.New("empty transaction")
}
......@@ -38,7 +37,7 @@ func isDepositTx(opaqueTx l2.Data) (bool, error) {
// 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.
// 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
for i, tx := range txns {
deposit, err := isDepositTx(tx)
......@@ -54,13 +53,13 @@ func lastDeposit(txns []l2.Data) (int, error) {
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())
if err := d.l2.NewPayload(ctx, payload); err != nil {
return fmt.Errorf("failed to insert new payload: %v", err)
}
// now try to persist a reorg to the new payload
fc := l2.ForkchoiceState{
fc := eth.ForkchoiceState{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: l2SafeHead.Hash,
FinalizedBlockHash: l2Finalized.Hash,
......@@ -69,13 +68,13 @@ func (d *outputImpl) processBlock(ctx context.Context, l2Head eth.L2BlockRef, l2
if err != nil {
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 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)
fetchCtx, cancel := context.WithTimeout(ctx, time.Second*20)
......@@ -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.
var txns []l2.Data
var txns []eth.Data
// First transaction in every block is always the L1 info transaction.
l1InfoTx, err := derive.L1InfoDepositBytes(seqNumber, l1Info)
......@@ -128,9 +127,9 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef,
shouldProduceEmptyBlock := nextL2Time >= l1Origin.Time+d.Config.MaxSequencerDrift
// Put together our payload attributes.
attrs := &l2.PayloadAttributes{
attrs := &eth.PayloadAttributes{
Timestamp: hexutil.Uint64(nextL2Time),
PrevRandao: l2.Bytes32(l1Info.MixDigest()),
PrevRandao: eth.Bytes32(l1Info.MixDigest()),
SuggestedFeeRecipient: d.Config.FeeRecipientAddress,
Transactions: txns,
NoTxPool: shouldProduceEmptyBlock,
......@@ -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
// updated as a result of executing the block based on the attributes described above.
fc := l2.ForkchoiceState{
fc := eth.ForkchoiceState{
HeadBlockHash: l2Head.Hash,
SafeBlockHash: l2SafeHead.Hash,
FinalizedBlockHash: l2Finalized.Hash,
......@@ -151,7 +150,7 @@ func (d *outputImpl) createNewBlock(ctx context.Context, l2Head eth.L2BlockRef,
}
// 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
}
......@@ -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.FillMissingBatches(batches, uint64(epoch), d.Config.BlockTime, minL2Time, nextL1Block.Time())
fc := l2.ForkchoiceState{
fc := eth.ForkchoiceState{
HeadBlockHash: l2Head.Hash,
SafeBlockHash: l2SafeHead.Hash,
FinalizedBlockHash: l2Finalized.Hash,
......@@ -224,10 +223,10 @@ func (d *outputImpl) insertEpoch(ctx context.Context, l2Head eth.L2BlockRef, l2S
lastHead := l2Head
lastSafeHead := l2SafeHead
didReorg := false
var payload *l2.ExecutionPayload
var payload *eth.ExecutionPayload
var reorg bool
for i, batch := range batches {
var txns []l2.Data
var txns []eth.Data
l1InfoTx, err := derive.L1InfoDepositBytes(uint64(i), l1Info)
if err != nil {
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
txns = append(txns, deposits...)
}
txns = append(txns, batch.Transactions...)
attrs := &l2.PayloadAttributes{
attrs := &eth.PayloadAttributes{
Timestamp: hexutil.Uint64(batch.Timestamp),
PrevRandao: l2.Bytes32(l1Info.MixDigest()),
PrevRandao: eth.Bytes32(l1Info.MixDigest()),
SuggestedFeeRecipient: d.Config.FeeRecipientAddress,
Transactions: txns,
// 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
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 {
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
// 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
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 {
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
// 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.
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))
if err != nil {
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 {
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,
// 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.
// 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)
if err != nil {
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)
}
id := fcRes.PayloadID
......@@ -403,7 +402,7 @@ func (d *outputImpl) insertHeadBlock(ctx context.Context, fc l2.ForkchoiceState,
if err != nil {
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 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 (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l2"
"github.com/ethereum-optimism/optimism/op-node/rollup"
)
......@@ -167,7 +166,7 @@ func (m *FakeChainSource) L2BlockRefByHash(ctx context.Context, l2Hash common.Ha
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.l2reorg++
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 (
"strings"
"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/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
......@@ -20,7 +20,7 @@ import (
)
var bigOne = big.NewInt(1)
var supportedL2OutputVersion = l2.Bytes32{}
var supportedL2OutputVersion = eth.Bytes32{}
type Config struct {
Log log.Logger
......@@ -261,16 +261,16 @@ func (d *Driver) SendTransaction(
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)
if err != nil {
return l2.Bytes32{}, err
return eth.Bytes32{}, err
}
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 {
return l2.Bytes32{}, fmt.Errorf("unsupported l2 output version")
return eth.Bytes32{}, fmt.Errorf("unsupported l2 output version")
}
return output[1], nil
}
......@@ -4,7 +4,7 @@ import (
"context"
"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/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
......@@ -28,8 +28,8 @@ func (r *RollupClient) GetBatchBundle(
return batchResponse, err
}
func (r *RollupClient) OutputAtBlock(ctx context.Context, blockNum *big.Int) ([]l2.Bytes32, error) {
var output []l2.Bytes32
func (r *RollupClient) OutputAtBlock(ctx context.Context, blockNum *big.Int) ([]eth.Bytes32, error) {
var output []eth.Bytes32
err := r.rpc.CallContext(ctx, &output, "optimism_outputAtBlock", hexutil.EncodeBig(blockNum))
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