• Evan Richard's avatar
    Ecotone/Dencun implementation changes (#8707) · 705db877
    Evan Richard authored
    * op-node: L2 Dencun implementation updates
    
    init branch
    
    Update reference to Eclipse/Ecotone in specs.
    
    Pull in Danyal's deposit source and add additional tests.
    
    Add notion of ParentBeaconRoot and build a contract deploy tx in attributes.go.
    
    Add a test for activating dencun l2 fork after genesis.
    
    Add draft ecotone setup.
    
    Add first pass of Eclipse upgrade txns
    
    Fix tests/compile
    
    Code review feedback
    
    Obey linter.
    
    Move ecotone setup to helpers.go; get the ParentBeaconBlockRoot from the l1Info in attributes.go.
    
    chore(op-node): Add tests for Ecotone deposit transactions (#8746)
    
    * Source hash teEvanJRichard <evan@oplabs.co>
    
    dencun review fixes
    
    derive: ecotone upgradeTo abi encoding
    
    op-e2e: test L2 exclusion of blob-txs in Ecotone
    
    op-node/rollup: deduplicate ecotone activation helper func, fix rollup config var name
    
    op-chain-ops: clarify 4788 contract nonce
    
    op-node/rollup: add setEcotone to ecotone upgrade txs
    
    dencun review fixes
    
    Dencun: P2P / EngineAPI / ExecutionPayloadEnvelope changes
    
    Includes:
    - Pass through execution payload (Envelope type everywhere) by Danyal,
      extended by Proto
    - Fix ecotone upgrade txns, by Danyal
    - ci fixes by Danyal
    - P2P Req/Resp (version based encoding/decoding) by Danyal
    - EngineAPI v3 usage by Danyl, rebased by Proto on EngineController (from
      trianglesphere)
    - Block v3 Gossip validation, by Danyal
    - Block v3 Gossip publishing, by Proto
    
    Rebased on updated Ecotone / Dencun base branch
    
    op-e2e: fix upgrade-txs count in test
    
    op-node: fix l1 info scalar migration, implement dencun review suggestions
    
    op-node: more dencun review nit fixes
    
    op-node: rabbit suggestions, but fixed
    
    Fix nil pointer in p2p sync for Ecotone blocks
    
    dencun: fix more nits
    
    op-e2e: fix lint
    Co-authored-by: default avatarDanyal Prout <me@dany.al>
    Co-authored-by: default avatarprotolambda <proto@protolambda.com>
    Co-authored-by: default avatarEvanJRichard <evan@oplabs.co>
    
    * Add tests for attribute matching
    
    * Provide a no-op blob fetcher to prevent derivation error
    
    Fail tests when there is an unknown error
    
    Fix typo / empty array to nil
    
    Update L2 tests to ecotone / add additional checks
    
    * op-e2e: dencun action-test setup fixes
    
    * op-node: op-conductor dencun todo
    
    * dencun: fix review nit about parent beacon block root gossip check style
    
    ---------
    Co-authored-by: default avatarDanyal Prout <me@dany.al>
    Co-authored-by: default avatarprotolambda <proto@protolambda.com>
    705db877
engine_test.go 7.31 KB
package l2

import (
	"context"
	"math/big"
	"testing"

	"github.com/ethereum-optimism/optimism/op-node/chaincfg"
	"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
	"github.com/ethereum-optimism/optimism/op-service/eth"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/consensus"
	"github.com/ethereum/go-ethereum/core/state"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/core/vm"
	"github.com/ethereum/go-ethereum/params"
	"github.com/ethereum/go-ethereum/trie"
	"github.com/stretchr/testify/require"
)

// Should implement derive.Engine
var _ derive.Engine = (*OracleEngine)(nil)

func TestPayloadByHash(t *testing.T) {
	ctx := context.Background()

	t.Run("KnownBlock", func(t *testing.T) {
		engine, stub := createOracleEngine(t)
		block := stub.head
		payload, err := engine.PayloadByHash(ctx, block.Hash())
		require.NoError(t, err)
		expected, err := eth.BlockAsPayload(block, engine.rollupCfg.CanyonTime)
		require.NoError(t, err)
		require.Equal(t, &eth.ExecutionPayloadEnvelope{ExecutionPayload: expected}, payload)
	})

	t.Run("UnknownBlock", func(t *testing.T) {
		engine, _ := createOracleEngine(t)
		hash := common.HexToHash("0x878899")
		payload, err := engine.PayloadByHash(ctx, hash)
		require.ErrorIs(t, err, ErrNotFound)
		require.Nil(t, payload)
	})
}

func TestPayloadByNumber(t *testing.T) {
	ctx := context.Background()

	t.Run("KnownBlock", func(t *testing.T) {
		engine, stub := createOracleEngine(t)
		block := stub.head
		payload, err := engine.PayloadByNumber(ctx, block.NumberU64())
		require.NoError(t, err)
		expected, err := eth.BlockAsPayload(block, engine.rollupCfg.CanyonTime)
		require.NoError(t, err)
		require.Equal(t, &eth.ExecutionPayloadEnvelope{ExecutionPayload: expected}, payload)
	})

	t.Run("NoCanonicalHash", func(t *testing.T) {
		engine, _ := createOracleEngine(t)
		payload, err := engine.PayloadByNumber(ctx, uint64(700))
		require.ErrorIs(t, err, ErrNotFound)
		require.Nil(t, payload)
	})

	t.Run("UnknownBlock", func(t *testing.T) {
		engine, stub := createOracleEngine(t)
		hash := common.HexToHash("0x878899")
		number := uint64(700)
		stub.canonical[number] = hash
		payload, err := engine.PayloadByNumber(ctx, number)
		require.ErrorIs(t, err, ErrNotFound)
		require.Nil(t, payload)
	})
}

func TestL2BlockRefByLabel(t *testing.T) {
	ctx := context.Background()
	engine, stub := createOracleEngine(t)
	tests := []struct {
		name  eth.BlockLabel
		block *types.Block
	}{
		{eth.Unsafe, stub.head},
		{eth.Safe, stub.safe},
		{eth.Finalized, stub.finalized},
	}
	for _, test := range tests {
		t.Run(string(test.name), func(t *testing.T) {
			expected, err := derive.L2BlockToBlockRef(engine.rollupCfg, test.block)
			require.NoError(t, err)
			blockRef, err := engine.L2BlockRefByLabel(ctx, test.name)
			require.NoError(t, err)
			require.Equal(t, expected, blockRef)
		})
	}
	t.Run("UnknownLabel", func(t *testing.T) {
		_, err := engine.L2BlockRefByLabel(ctx, "nope")
		require.ErrorContains(t, err, "unknown label")
	})
}

func TestL2BlockRefByHash(t *testing.T) {
	ctx := context.Background()
	engine, stub := createOracleEngine(t)

	t.Run("KnownBlock", func(t *testing.T) {
		expected, err := derive.L2BlockToBlockRef(engine.rollupCfg, stub.safe)
		require.NoError(t, err)
		ref, err := engine.L2BlockRefByHash(ctx, stub.safe.Hash())
		require.NoError(t, err)
		require.Equal(t, expected, ref)
	})

	t.Run("UnknownBlock", func(t *testing.T) {
		ref, err := engine.L2BlockRefByHash(ctx, common.HexToHash("0x878899"))
		require.ErrorIs(t, err, ErrNotFound)
		require.Equal(t, eth.L2BlockRef{}, ref)
	})
}

func TestSystemConfigByL2Hash(t *testing.T) {
	ctx := context.Background()
	engine, stub := createOracleEngine(t)

	t.Run("KnownBlock", func(t *testing.T) {
		payload, err := eth.BlockAsPayload(stub.safe, engine.rollupCfg.CanyonTime)
		require.NoError(t, err)
		expected, err := derive.PayloadToSystemConfig(engine.rollupCfg, payload)
		require.NoError(t, err)
		cfg, err := engine.SystemConfigByL2Hash(ctx, stub.safe.Hash())
		require.NoError(t, err)
		require.Equal(t, expected, cfg)
	})

	t.Run("UnknownBlock", func(t *testing.T) {
		ref, err := engine.SystemConfigByL2Hash(ctx, common.HexToHash("0x878899"))
		require.ErrorIs(t, err, ErrNotFound)
		require.Equal(t, eth.SystemConfig{}, ref)
	})
}

func createOracleEngine(t *testing.T) (*OracleEngine, *stubEngineBackend) {
	head := createL2Block(t, 4)
	safe := createL2Block(t, 3)
	finalized := createL2Block(t, 2)
	backend := &stubEngineBackend{
		head:      head,
		safe:      safe,
		finalized: finalized,
		blocks: map[common.Hash]*types.Block{
			head.Hash():      head,
			safe.Hash():      safe,
			finalized.Hash(): finalized,
		},
		canonical: map[uint64]common.Hash{
			head.NumberU64():      head.Hash(),
			safe.NumberU64():      safe.Hash(),
			finalized.NumberU64(): finalized.Hash(),
		},
	}
	engine := OracleEngine{
		backend:   backend,
		rollupCfg: chaincfg.Goerli,
	}
	return &engine, backend
}

func createL2Block(t *testing.T, number int) *types.Block {
	tx, err := derive.L1InfoDeposit(chaincfg.Goerli, eth.SystemConfig{}, uint64(1), eth.HeaderBlockInfo(&types.Header{
		Number:  big.NewInt(32),
		BaseFee: big.NewInt(7),
	}), 0)
	require.NoError(t, err)
	header := &types.Header{
		Number:  big.NewInt(int64(number)),
		BaseFee: big.NewInt(7),
	}
	return types.NewBlock(header, []*types.Transaction{types.NewTx(tx)}, nil, nil, trie.NewStackTrie(nil))
}

type stubEngineBackend struct {
	head      *types.Block
	safe      *types.Block
	finalized *types.Block
	blocks    map[common.Hash]*types.Block
	canonical map[uint64]common.Hash
}

func (s stubEngineBackend) CurrentHeader() *types.Header {
	return s.head.Header()
}

func (s stubEngineBackend) CurrentSafeBlock() *types.Header {
	return s.safe.Header()
}

func (s stubEngineBackend) CurrentFinalBlock() *types.Header {
	return s.finalized.Header()
}

func (s stubEngineBackend) GetBlockByHash(hash common.Hash) *types.Block {
	return s.blocks[hash]
}

func (s stubEngineBackend) GetCanonicalHash(n uint64) common.Hash {
	return s.canonical[n]
}

func (s stubEngineBackend) GetBlock(hash common.Hash, number uint64) *types.Block {
	panic("unsupported")
}

func (s stubEngineBackend) HasBlockAndState(hash common.Hash, number uint64) bool {
	panic("unsupported")
}

func (s stubEngineBackend) GetVMConfig() *vm.Config {
	panic("unsupported")
}

func (s stubEngineBackend) Config() *params.ChainConfig {
	panic("unsupported")
}

func (s stubEngineBackend) Engine() consensus.Engine {
	panic("unsupported")
}

func (s stubEngineBackend) StateAt(root common.Hash) (*state.StateDB, error) {
	panic("unsupported")
}

func (s stubEngineBackend) InsertBlockWithoutSetHead(block *types.Block) error {
	panic("unsupported")
}

func (s stubEngineBackend) SetCanonical(head *types.Block) (common.Hash, error) {
	panic("unsupported")
}

func (s stubEngineBackend) SetFinalized(header *types.Header) {
	panic("unsupported")
}

func (s stubEngineBackend) SetSafe(header *types.Header) {
	panic("unsupported")
}

func (s stubEngineBackend) GetHeader(hash common.Hash, number uint64) *types.Header {
	panic("unsupported")
}

func (s stubEngineBackend) GetHeaderByNumber(number uint64) *types.Header {
	panic("unsupported")
}

func (s stubEngineBackend) GetHeaderByHash(hash common.Hash) *types.Header {
	panic("unsupported")
}

func (s stubEngineBackend) GetTd(hash common.Hash, number uint64) *big.Int {
	panic("unsupported")
}