Commit f917b007 authored by protolambda's avatar protolambda

op-e2e: action tests: L2 engine actor

parent e81a6ff5
package actions
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
geth "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/testutils"
)
// L2Engine is an in-memory implementation of the Engine API,
// without support for snap-sync, and no concurrency or background processes.
type L2Engine struct {
log log.Logger
node *node.Node
eth *geth.Ethereum
rollupGenesis *rollup.Genesis
// L2 evm / chain
l2Chain *core.BlockChain
l2Database ethdb.Database
l2Cfg *core.Genesis
l2Signer types.Signer
// L2 block building data
// TODO proto - block building PR
payloadID beacon.PayloadID // ID of payload that is currently being built
failL2RPC error // mock error
}
func NewL2Engine(log log.Logger, genesis *core.Genesis, rollupGenesisL1 eth.BlockID, jwtPath string) *L2Engine {
ethCfg := &ethconfig.Config{
NetworkId: genesis.Config.ChainID.Uint64(),
Genesis: genesis,
}
nodeCfg := &node.Config{
Name: "l2-geth",
WSHost: "127.0.0.1",
WSPort: 0,
AuthAddr: "127.0.0.1",
AuthPort: 0,
WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal"},
HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal"},
JWTSecret: jwtPath,
}
n, err := node.New(nodeCfg)
if err != nil {
panic(err)
}
backend, err := geth.New(n, ethCfg)
if err != nil {
panic(err)
}
n.RegisterAPIs(tracers.APIs(backend.APIBackend))
chain := backend.BlockChain()
db := backend.ChainDb()
genesisBlock := chain.Genesis()
eng := &L2Engine{
log: log,
node: n,
eth: backend,
rollupGenesis: &rollup.Genesis{
L1: rollupGenesisL1,
L2: eth.BlockID{Hash: genesisBlock.Hash(), Number: genesisBlock.NumberU64()},
L2Time: genesis.Timestamp,
},
l2Chain: chain,
l2Database: db,
l2Cfg: genesis,
l2Signer: types.LatestSigner(genesis.Config),
}
// register the custom engine API, so we can serve engine requests while having more control
// over sequencing of individual txs.
n.RegisterAPIs([]rpc.API{
{
Namespace: "engine",
Service: (*L2EngineAPI)(eng),
Authenticated: true,
},
})
if err := n.Start(); err != nil {
panic(fmt.Errorf("failed to start L2 op-geth node: %w", err))
}
return eng
}
func (e *L2Engine) RPCClient() client.RPC {
cl, _ := e.node.Attach() // never errors
return testutils.RPCErrFaker{
RPC: cl,
ErrFn: func() error {
err := e.failL2RPC
e.failL2RPC = nil // reset back, only error once.
return err
},
}
}
// ActL2RPCFail makes the next L2 RPC request fail
func (e *L2Engine) ActL2RPCFail(t Testing) {
if e.failL2RPC != nil { // already set to fail?
t.InvalidAction("already set a mock L2 rpc error")
return
}
e.failL2RPC = errors.New("mock L2 RPC error")
}
This diff is collapsed.
package actions
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
)
func TestL2EngineAPI(gt *testing.T) {
t := NewDefaultTesting(gt)
jwtPath := e2eutils.WriteDefaultJWT(t)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
genesisBlock := sd.L2Cfg.ToBlock()
consensus := beacon.New(ethash.NewFaker())
db := rawdb.NewMemoryDatabase()
sd.L2Cfg.MustCommit(db)
engine := NewL2Engine(log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
l2Cl, err := sources.NewEngineClient(engine.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
// build an empty block
chainA, _ := core.GenerateChain(sd.L2Cfg.Config, genesisBlock, consensus, db, 1, func(i int, gen *core.BlockGen) {
gen.SetCoinbase(common.Address{'A'})
})
payloadA, err := eth.BlockAsPayload(chainA[0])
require.NoError(t, err)
// apply the payload
status, err := l2Cl.NewPayload(t.Ctx(), payloadA)
require.NoError(t, err)
require.Equal(t, status.Status, eth.ExecutionValid)
require.Equal(t, genesisBlock.Hash(), engine.l2Chain.CurrentBlock().Hash(), "processed payloads are not immediately canonical")
// recognize the payload as canonical
fcRes, err := l2Cl.ForkchoiceUpdate(t.Ctx(), &eth.ForkchoiceState{
HeadBlockHash: payloadA.BlockHash,
SafeBlockHash: genesisBlock.Hash(),
FinalizedBlockHash: genesisBlock.Hash(),
}, nil)
require.NoError(t, err)
require.Equal(t, fcRes.PayloadStatus.Status, eth.ExecutionValid)
require.Equal(t, payloadA.BlockHash, engine.l2Chain.CurrentBlock().Hash(), "now payload A is canonical")
// build an alternative block
chainB, _ := core.GenerateChain(sd.L2Cfg.Config, genesisBlock, consensus, db, 1, func(i int, gen *core.BlockGen) {
gen.SetCoinbase(common.Address{'B'})
})
payloadB, err := eth.BlockAsPayload(chainB[0])
require.NoError(t, err)
// apply the payload
status, err = l2Cl.NewPayload(t.Ctx(), payloadB)
require.NoError(t, err)
require.Equal(t, status.Status, eth.ExecutionValid)
require.Equal(t, payloadA.BlockHash, engine.l2Chain.CurrentBlock().Hash(), "processed payloads are not immediately canonical")
// reorg block A in favor of block B
fcRes, err = l2Cl.ForkchoiceUpdate(t.Ctx(), &eth.ForkchoiceState{
HeadBlockHash: payloadB.BlockHash,
SafeBlockHash: genesisBlock.Hash(),
FinalizedBlockHash: genesisBlock.Hash(),
}, nil)
require.NoError(t, err)
require.Equal(t, fcRes.PayloadStatus.Status, eth.ExecutionValid)
require.Equal(t, payloadB.BlockHash, engine.l2Chain.CurrentBlock().Hash(), "now payload B is canonical")
}
func TestL2EngineAPIFail(gt *testing.T) {
t := NewDefaultTesting(gt)
jwtPath := e2eutils.WriteDefaultJWT(t)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
engine := NewL2Engine(log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
// mock an RPC failure
engine.ActL2RPCFail(t)
// check RPC failure
l2Cl, err := sources.NewL2Client(engine.RPCClient(), log, nil, sources.L2ClientDefaultConfig(sd.RollupCfg, false))
require.NoError(t, err)
_, err = l2Cl.InfoByLabel(t.Ctx(), eth.Unsafe)
require.ErrorContains(t, err, "mock")
head, err := l2Cl.InfoByLabel(t.Ctx(), eth.Unsafe)
require.NoError(t, err)
require.Equal(gt, sd.L2Cfg.ToBlock().Hash(), head.Hash(), "expecting engine to start at genesis")
}
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