Commit bfb1ad93 authored by protolambda's avatar protolambda Committed by GitHub

op-supervisor,op-e2e: op-supervisor action test (#12691)

parent 36b41bae
......@@ -72,7 +72,7 @@ func NewL2AltDA(t helpers.Testing, params ...AltDAParam) *L2AltDA {
l1Client := miner.EthClient()
jwtPath := e2eutils.WriteDefaultJWT(t)
engine := helpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
engine := helpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath)
engCl := engine.EngineClient(t, sd.RollupCfg)
storage := &altda.DAErrFaker{Client: altda.NewMockDAClient(log)}
......@@ -136,7 +136,7 @@ func (a *L2AltDA) StorageClient() *altda.DAErrFaker {
func (a *L2AltDA) NewVerifier(t helpers.Testing) *helpers.L2Verifier {
jwtPath := e2eutils.WriteDefaultJWT(t)
engine := helpers.NewL2Engine(t, a.log, a.sd.L2Cfg, a.sd.RollupCfg.Genesis.L1, jwtPath)
engine := helpers.NewL2Engine(t, a.log, a.sd.L2Cfg, jwtPath)
engCl := engine.EngineClient(t, a.sd.RollupCfg)
l1F, err := sources.NewL1Client(a.miner.RPCClient(), a.log, nil, sources.L1ClientDefaultConfig(a.sd.RollupCfg, false, sources.RPCKindBasic))
require.NoError(t, err)
......
......@@ -592,7 +592,7 @@ func RestartOpGeth(gt *testing.T, deltaTimeOffset *hexutil.Uint64) {
l1F, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard))
require.NoError(t, err)
// Sequencer
seqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, dbOption)
seqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath, dbOption)
engRpc := &rpcWrapper{seqEng.RPCClient()}
l2Cl, err := sources.NewEngineClient(engRpc, log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
......@@ -647,7 +647,7 @@ func RestartOpGeth(gt *testing.T, deltaTimeOffset *hexutil.Uint64) {
// close the sequencer engine
require.NoError(t, seqEng.Close())
// and start a new one with same db path
seqEngNew := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, dbOption)
seqEngNew := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath, dbOption)
// swap in the new rpc. This is as close as we can get to reconnecting to a new in-memory rpc connection
engRpc.RPC = seqEngNew.RPCClient()
......@@ -679,7 +679,7 @@ func ConflictingL2Blocks(gt *testing.T, deltaTimeOffset *hexutil.Uint64) {
// Extra setup: a full alternative sequencer, sequencer engine, and batcher
jwtPath := e2eutils.WriteDefaultJWT(t)
altSeqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
altSeqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath)
altSeqEngCl, err := sources.NewEngineClient(altSeqEng.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
l1F, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard))
......
......@@ -24,7 +24,6 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testutils"
)
......@@ -37,8 +36,6 @@ type L2Engine struct {
node *node.Node
Eth *geth.Ethereum
rollupGenesis *rollup.Genesis
// L2 evm / chain
l2Chain *core.BlockChain
l2Signer types.Signer
......@@ -50,20 +47,14 @@ type L2Engine struct {
type EngineOption func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error
func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, rollupGenesisL1 eth.BlockID, jwtPath string, options ...EngineOption) *L2Engine {
func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, jwtPath string, options ...EngineOption) *L2Engine {
n, ethBackend, apiBackend := newBackend(t, genesis, jwtPath, options)
engineApi := engineapi.NewL2EngineAPI(log, apiBackend, ethBackend.Downloader())
chain := ethBackend.BlockChain()
genesisBlock := chain.Genesis()
eng := &L2Engine{
log: log,
node: n,
Eth: ethBackend,
rollupGenesis: &rollup.Genesis{
L1: rollupGenesisL1,
L2: eth.BlockID{Hash: genesisBlock.Hash(), Number: genesisBlock.NumberU64()},
L2Time: genesis.Timestamp,
},
log: log,
node: n,
Eth: ethBackend,
l2Chain: chain,
l2Signer: types.LatestSigner(genesis.Config),
EngineApi: engineApi,
......
......@@ -40,7 +40,7 @@ func TestL2EngineAPI(gt *testing.T) {
tdb := triedb.NewDatabase(db, &triedb.Config{HashDB: hashdb.Defaults})
sd.L2Cfg.MustCommit(db, tdb)
engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
engine := NewL2Engine(t, log, sd.L2Cfg, jwtPath)
l2Cl, err := sources.NewEngineClient(engine.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
......@@ -115,7 +115,7 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) {
tdb := triedb.NewDatabase(db, &triedb.Config{HashDB: hashdb.Defaults})
sd.L2Cfg.MustCommit(db, tdb)
engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
engine := NewL2Engine(t, log, sd.L2Cfg, jwtPath)
t.Cleanup(func() {
_ = engine.Close()
})
......@@ -211,7 +211,7 @@ func TestL2EngineAPIFail(gt *testing.T) {
dp := e2eutils.MakeDeployParams(t, DefaultRollupTestParams())
sd := e2eutils.Setup(t, dp, DefaultAlloc)
log := testlog.Logger(t, log.LevelDebug)
engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
engine := NewL2Engine(t, log, sd.L2Cfg, jwtPath)
// mock an RPC failure
mockErr := errors.New("mock L2 RPC error")
engine.ActL2RPCFail(t, mockErr)
......
......@@ -25,7 +25,7 @@ func SetupSequencerTest(t Testing, sd *e2eutils.SetupData, log log.Logger, opts
l1F, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard))
require.NoError(t, err)
engine := NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, EngineWithP2P())
engine := NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, jwtPath, EngineWithP2P())
l2Cl, err := sources.NewEngineClient(engine.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
......@@ -40,7 +40,7 @@ func SetupVerifier(t Testing, sd *e2eutils.SetupData, log log.Logger,
opt(cfg)
}
jwtPath := e2eutils.WriteDefaultJWT(t)
engine := NewL2Engine(t, log.New("role", "verifier-engine"), sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, EngineWithP2P())
engine := NewL2Engine(t, log.New("role", "verifier-engine"), sd.L2Cfg, jwtPath, EngineWithP2P())
engCl := engine.EngineClient(t, sd.RollupCfg)
verifier := NewL2Verifier(t, log.New("role", "verifier"), l1F, blobSrc, altda.Disabled, engCl, sd.RollupCfg, syncCfg, cfg.SafeHeadListener, cfg.InteropBackend)
return engine, verifier
......
package interop
import (
"context"
"os"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
altda "github.com/ethereum-optimism/optimism/op-alt-da"
batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/interopgen"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/interop"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/metrics"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
const (
foundryArtifactsDir = "../../../packages/contracts-bedrock/forge-artifacts"
sourceMapDir = "../../../packages/contracts-bedrock"
)
// Chain holds the most common per-chain action-test data and actors
type Chain struct {
ChainID types.ChainID
RollupCfg *rollup.Config
ChainCfg *params.ChainConfig
BatcherAddr common.Address
Sequencer *helpers.L2Sequencer
SequencerEngine *helpers.L2Engine
Batcher *helpers.L2Batcher
}
// InteropSetup holds the chain deployment and config contents, before instantiating any services.
type InteropSetup struct {
Log log.Logger
Deployment *interopgen.WorldDeployment
Out *interopgen.WorldOutput
DepSet *depset.StaticConfigDependencySet
Keys devkeys.Keys
T helpers.Testing
}
// InteropActors holds a bundle of global actors and actors of 2 chains.
type InteropActors struct {
L1Miner *helpers.L1Miner
Supervisor *SupervisorActor
ChainA *Chain
ChainB *Chain
}
// SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains.
func SetupInterop(t helpers.Testing) *InteropSetup {
logger := testlog.Logger(t, log.LevelDebug)
recipe := interopgen.InteropDevRecipe{
L1ChainID: 900100,
L2ChainIDs: []uint64{900200, 900201},
GenesisTimestamp: uint64(time.Now().Unix() + 3),
}
hdWallet, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic)
require.NoError(t, err)
worldCfg, err := recipe.Build(hdWallet)
require.NoError(t, err)
// create the foundry artifacts and source map
foundryArtifacts := foundry.OpenArtifactsDir(foundryArtifactsDir)
sourceMap := foundry.NewSourceMapFS(os.DirFS(sourceMapDir))
// deploy the world, using the logger, foundry artifacts, source map, and world configuration
worldDeployment, worldOutput, err := interopgen.Deploy(logger, foundryArtifacts, sourceMap, worldCfg)
require.NoError(t, err)
return &InteropSetup{
Log: logger,
Deployment: worldDeployment,
Out: worldOutput,
DepSet: worldToDepSet(t, worldOutput),
Keys: hdWallet,
T: t,
}
}
func (is *InteropSetup) CreateActors() *InteropActors {
l1Miner := helpers.NewL1Miner(is.T, is.Log.New("role", "l1Miner"), is.Out.L1.Genesis)
supervisorAPI := NewSupervisor(is.T, is.Log, is.DepSet)
require.NoError(is.T, supervisorAPI.Start(is.T.Ctx()))
is.T.Cleanup(func() {
require.NoError(is.T, supervisorAPI.Stop(context.Background()))
})
chainA := createL2Services(is.T, is.Log, l1Miner, is.Keys, is.Out.L2s["900200"], supervisorAPI)
chainB := createL2Services(is.T, is.Log, l1Miner, is.Keys, is.Out.L2s["900201"], supervisorAPI)
// Hook up L2 RPCs to supervisor, to fetch event data from
require.NoError(is.T, supervisorAPI.AddL2RPC(is.T.Ctx(), chainA.SequencerEngine.HTTPEndpoint()))
require.NoError(is.T, supervisorAPI.AddL2RPC(is.T.Ctx(), chainB.SequencerEngine.HTTPEndpoint()))
return &InteropActors{
L1Miner: l1Miner,
Supervisor: supervisorAPI,
ChainA: chainA,
ChainB: chainB,
}
}
// SupervisorActor represents a supervisor, instrumented to run synchronously for action-test purposes.
type SupervisorActor struct {
backend *backend.SupervisorBackend
frontend.QueryFrontend
frontend.AdminFrontend
frontend.UpdatesFrontend
}
func (sa *SupervisorActor) SyncEvents(t helpers.Testing, chainID types.ChainID) {
require.NoError(t, sa.backend.SyncEvents(chainID))
}
func (sa *SupervisorActor) SyncCrossUnsafe(t helpers.Testing, chainID types.ChainID) {
require.NoError(t, sa.backend.SyncCrossUnsafe(chainID))
}
func (sa *SupervisorActor) SyncCrossSafe(t helpers.Testing, chainID types.ChainID) {
require.NoError(t, sa.backend.SyncCrossSafe(chainID))
}
// worldToDepSet converts a set of chain configs into a dependency-set for the supervisor.
func worldToDepSet(t helpers.Testing, worldOutput *interopgen.WorldOutput) *depset.StaticConfigDependencySet {
depSetCfg := make(map[types.ChainID]*depset.StaticConfigDependency)
for _, out := range worldOutput.L2s {
depSetCfg[types.ChainIDFromBig(out.Genesis.Config.ChainID)] = &depset.StaticConfigDependency{
ChainIndex: types.ChainIndex(out.Genesis.Config.ChainID.Uint64()),
ActivationTime: 0,
HistoryMinTime: 0,
}
}
depSet, err := depset.NewStaticConfigDependencySet(depSetCfg)
require.NoError(t, err)
return depSet
}
// NewSupervisor creates a new SupervisorActor, to action-test the supervisor with.
func NewSupervisor(t helpers.Testing, logger log.Logger, depSet depset.DependencySetSource) *SupervisorActor {
logger = logger.New("role", "supervisor")
supervisorDataDir := t.TempDir()
logger.Info("supervisor data dir", "dir", supervisorDataDir)
svCfg := &config.Config{
DependencySetSource: depSet,
SynchronousProcessors: true,
Datadir: supervisorDataDir,
}
b, err := backend.NewSupervisorBackend(t.Ctx(),
logger.New("role", "supervisor"), metrics.NoopMetrics, svCfg)
require.NoError(t, err)
return &SupervisorActor{
backend: b,
QueryFrontend: frontend.QueryFrontend{
Supervisor: b,
},
AdminFrontend: frontend.AdminFrontend{
Supervisor: b,
},
UpdatesFrontend: frontend.UpdatesFrontend{
Supervisor: b,
},
}
}
// createL2Services creates a Chain bundle, with the given configs, and attached to the given L1 miner.
func createL2Services(
t helpers.Testing,
logger log.Logger,
l1Miner *helpers.L1Miner,
keys devkeys.Keys,
output *interopgen.L2Output,
interopBackend interop.InteropBackend,
) *Chain {
logger = logger.New("chain", output.Genesis.Config.ChainID)
jwtPath := e2eutils.WriteDefaultJWT(t)
eng := helpers.NewL2Engine(t, logger.New("role", "engine"), output.Genesis, jwtPath)
seqCl, err := sources.NewEngineClient(eng.RPCClient(), logger, nil, sources.EngineClientDefaultConfig(output.RollupCfg))
require.NoError(t, err)
l1F, err := sources.NewL1Client(l1Miner.RPCClient(), logger, nil,
sources.L1ClientDefaultConfig(output.RollupCfg, false, sources.RPCKindStandard))
require.NoError(t, err)
seq := helpers.NewL2Sequencer(t, logger.New("role", "sequencer"), l1F,
l1Miner.BlobStore(), altda.Disabled, seqCl, output.RollupCfg,
0, interopBackend)
batcherKey, err := keys.Secret(devkeys.ChainOperatorKey{
ChainID: output.Genesis.Config.ChainID,
Role: devkeys.BatcherRole,
})
require.NoError(t, err)
batcherCfg := &helpers.BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: batcherKey,
DataAvailabilityType: batcherFlags.CalldataType,
}
batcher := helpers.NewL2Batcher(logger.New("role", "batcher"), output.RollupCfg, batcherCfg,
seq.RollupClient(), l1Miner.EthClient(),
eng.EthClient(), eng.EngineClient(t, output.RollupCfg))
return &Chain{
ChainID: types.ChainIDFromBig(output.Genesis.Config.ChainID),
RollupCfg: output.RollupCfg,
ChainCfg: output.Genesis.Config,
BatcherAddr: crypto.PubkeyToAddress(batcherKey.PublicKey),
Sequencer: seq,
SequencerEngine: eng,
Batcher: batcher,
}
}
package interop
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/interop"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
var _ interop.InteropBackend = (*testutils.FakeInteropBackend)(nil)
func TestInteropVerifier(gt *testing.T) {
func TestFullInterop(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, helpers.DefaultRollupTestParams())
sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc)
// Temporary work-around: interop needs to be active, for cross-safety to not be instant.
// The state genesis in this test is pre-interop however.
sd.RollupCfg.InteropTime = new(uint64)
logger := testlog.Logger(t, log.LevelDebug)
seqMockBackend := &testutils.FakeInteropBackend{}
l1Miner, seqEng, seq := helpers.SetupSequencerTest(t, sd, logger,
helpers.WithVerifierOpts(helpers.WithInteropBackend(seqMockBackend)))
batcher := helpers.NewL2Batcher(logger, sd.RollupCfg, helpers.DefaultBatcherCfg(dp),
seq.RollupClient(), l1Miner.EthClient(), seqEng.EthClient(), seqEng.EngineClient(t, sd.RollupCfg))
verMockBackend := &testutils.FakeInteropBackend{}
_, ver := helpers.SetupVerifier(t, sd, logger,
l1Miner.L1Client(t, sd.RollupCfg), l1Miner.BlobStore(), &sync.Config{},
helpers.WithInteropBackend(verMockBackend))
// Genesis block will be registered as local-safe when we first trigger the derivation pipeline
seqMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error {
require.Equal(t, sd.RollupCfg.Genesis.L1, derivedFrom.ID())
require.Equal(t, sd.RollupCfg.Genesis.L2, lastDerived.ID())
return nil
}
verMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error {
require.Equal(t, sd.RollupCfg.Genesis.L1, derivedFrom.ID())
require.Equal(t, sd.RollupCfg.Genesis.L2, lastDerived.ID())
return nil
}
seq.ActL2PipelineFull(t)
ver.ActL2PipelineFull(t)
l2ChainID := types.ChainIDFromBig(sd.RollupCfg.L2ChainID)
seqMockBackend.UpdateLocalUnsafeFn = func(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error {
require.Equal(t, chainID, l2ChainID)
require.Equal(t, uint64(1), head.Number)
return nil
}
seqMockBackend.UnsafeViewFn = func(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) {
require.Equal(t, chainID, l2ChainID)
require.Equal(t, uint64(1), unsafe.Local.Number)
require.Equal(t, uint64(0), unsafe.Cross.Number)
return unsafe, nil
}
// create an unsafe L2 block
seq.ActL2StartBlock(t)
seq.ActL2EndBlock(t)
seq.ActL2PipelineFull(t)
status := seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
is := SetupInterop(t)
actors := is.CreateActors()
// get both sequencers set up
actors.ChainA.Sequencer.ActL2PipelineFull(t)
actors.ChainB.Sequencer.ActL2PipelineFull(t)
// No blocks yet
status := actors.ChainA.Sequencer.SyncStatus()
require.Equal(t, uint64(0), status.UnsafeL2.Number)
// sync chain A
actors.Supervisor.SyncEvents(t, actors.ChainA.ChainID)
actors.Supervisor.SyncCrossUnsafe(t, actors.ChainA.ChainID)
actors.Supervisor.SyncCrossSafe(t, actors.ChainA.ChainID)
// sync chain B
actors.Supervisor.SyncEvents(t, actors.ChainB.ChainID)
actors.Supervisor.SyncCrossUnsafe(t, actors.ChainB.ChainID)
actors.Supervisor.SyncCrossSafe(t, actors.ChainB.ChainID)
// Build L2 block on chain A
actors.ChainA.Sequencer.ActL2StartBlock(t)
actors.ChainA.Sequencer.ActL2EndBlock(t)
status = actors.ChainA.Sequencer.SyncStatus()
head := status.UnsafeL2.ID()
require.Equal(t, uint64(1), head.Number)
require.Equal(t, uint64(0), status.CrossUnsafeL2.Number)
require.Equal(t, uint64(0), status.LocalSafeL2.Number)
require.Equal(t, uint64(0), status.SafeL2.Number)
require.Equal(t, uint64(0), status.FinalizedL2.Number)
// promote it to cross-unsafe in the backend
// and see if the node picks up on it
seqMockBackend.UnsafeViewFn = func(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) {
require.Equal(t, chainID, l2ChainID)
require.Equal(t, uint64(1), unsafe.Local.Number)
require.Equal(t, uint64(0), unsafe.Cross.Number)
out := unsafe
out.Cross = unsafe.Local
return out, nil
}
seq.ActInteropBackendCheck(t)
seq.ActL2PipelineFull(t)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
require.Equal(t, uint64(1), status.CrossUnsafeL2.Number, "cross unsafe now")
// Verify as cross-unsafe with supervisor
actors.Supervisor.SyncEvents(t, actors.ChainA.ChainID)
actors.Supervisor.SyncCrossUnsafe(t, actors.ChainA.ChainID)
actors.ChainA.Sequencer.ActL2PipelineFull(t)
status = actors.ChainA.Sequencer.SyncStatus()
require.Equal(t, head, status.UnsafeL2.ID())
require.Equal(t, head, status.CrossUnsafeL2.ID())
require.Equal(t, uint64(0), status.LocalSafeL2.Number)
require.Equal(t, uint64(0), status.SafeL2.Number)
require.Equal(t, uint64(0), status.FinalizedL2.Number)
// submit all new L2 blocks
batcher.ActSubmitAll(t)
// new L1 block with L2 batch
l1Miner.ActL1StartBlock(12)(t)
l1Miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t)
l1Miner.ActL1EndBlock(t)
// Sync the L1 block, to verify the L2 block as local-safe.
seqMockBackend.UpdateLocalUnsafeFn = nil
nextL2 := uint64(0)
seqMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error {
require.Equal(t, nextL2, lastDerived.Number)
nextL2 += 1
return nil
}
seqMockBackend.SafeViewFn = func(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) {
require.Equal(t, chainID, l2ChainID)
require.Equal(t, uint64(1), safe.Local.Number)
require.Equal(t, uint64(0), safe.Cross.Number)
return safe, nil
}
seq.ActL1HeadSignal(t)
l1Head := seq.SyncStatus().HeadL1
seq.ActL2PipelineFull(t)
require.Equal(t, uint64(2), nextL2)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
require.Equal(t, uint64(1), status.CrossUnsafeL2.Number)
require.Equal(t, uint64(1), status.LocalSafeL2.Number, "local safe changed")
// Submit the L2 block, sync the local-safe data
actors.ChainA.Batcher.ActSubmitAll(t)
actors.L1Miner.ActL1StartBlock(12)(t)
actors.L1Miner.ActL1IncludeTx(actors.ChainA.BatcherAddr)(t)
actors.L1Miner.ActL1EndBlock(t)
actors.ChainA.Sequencer.ActL1HeadSignal(t)
actors.ChainA.Sequencer.ActL2PipelineFull(t)
status = actors.ChainA.Sequencer.SyncStatus()
require.Equal(t, head, status.UnsafeL2.ID())
require.Equal(t, head, status.CrossUnsafeL2.ID())
require.Equal(t, head, status.LocalSafeL2.ID())
require.Equal(t, uint64(0), status.SafeL2.Number)
// Now mark it as cross-safe
seqMockBackend.SafeViewFn = func(ctx context.Context, chainID types.ChainID, request types.ReferenceView) (types.ReferenceView, error) {
require.Equal(t, chainID, l2ChainID)
require.Equal(t, uint64(1), request.Local.Number)
require.Equal(t, uint64(0), request.Cross.Number)
out := request
out.Cross = request.Local
return out, nil
}
seqMockBackend.DerivedFromFn = func(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (eth.L1BlockRef, error) {
require.Equal(t, uint64(1), derived.Number)
return l1Head, nil
}
seqMockBackend.FinalizedFn = func(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) {
return seq.RollupCfg.Genesis.L1, nil
}
seq.ActInteropBackendCheck(t)
seq.ActL2PipelineFull(t)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
require.Equal(t, uint64(1), status.CrossUnsafeL2.Number)
require.Equal(t, uint64(1), status.LocalSafeL2.Number)
require.Equal(t, uint64(1), status.SafeL2.Number, "cross-safe reached")
require.Equal(t, uint64(0), status.FinalizedL2.Number)
verMockBackend.UpdateLocalUnsafeFn = func(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error {
require.Equal(t, uint64(1), head.Number)
return nil
}
nextL2 = 0
verMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error {
require.Equal(t, nextL2, lastDerived.Number)
nextL2 += 1
require.Equal(t, l1Head.ID(), derivedFrom.ID())
return nil
}
// The verifier might not see the L2 block that was just derived from L1 as cross-verified yet.
verMockBackend.UnsafeViewFn = func(ctx context.Context, chainID types.ChainID, request types.ReferenceView) (types.ReferenceView, error) {
require.Equal(t, uint64(1), request.Local.Number)
require.Equal(t, uint64(0), request.Cross.Number)
// Don't promote the Cross value yet
return request, nil
}
verMockBackend.SafeViewFn = func(ctx context.Context, chainID types.ChainID, request types.ReferenceView) (types.ReferenceView, error) {
require.Equal(t, uint64(1), request.Local.Number)
require.Equal(t, uint64(0), request.Cross.Number)
// Don't promote the Cross value yet
return request, nil
}
ver.ActL1HeadSignal(t)
ver.ActL2PipelineFull(t)
require.Equal(t, uint64(2), nextL2)
status = ver.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number, "synced the block")
require.Equal(t, uint64(0), status.CrossUnsafeL2.Number, "not cross-verified yet")
require.Equal(t, uint64(1), status.LocalSafeL2.Number, "derived from L1, thus local-safe")
require.Equal(t, uint64(0), status.SafeL2.Number, "not yet cross-safe")
// Local-safe does not count as "safe" in RPC
n := actors.ChainA.SequencerEngine.L2Chain().CurrentSafeBlock().Number.Uint64()
require.Equal(t, uint64(0), n)
// Cross-safe verify it
actors.Supervisor.SyncCrossSafe(t, actors.ChainA.ChainID)
actors.ChainA.Sequencer.ActInteropBackendCheck(t)
actors.ChainA.Sequencer.ActL2PipelineFull(t)
status = actors.ChainA.Sequencer.SyncStatus()
require.Equal(t, head, status.UnsafeL2.ID())
require.Equal(t, head, status.CrossUnsafeL2.ID())
require.Equal(t, head, status.LocalSafeL2.ID())
require.Equal(t, head, status.SafeL2.ID())
require.Equal(t, uint64(0), status.FinalizedL2.Number)
seqMockBackend.UpdateFinalizedL1Fn = func(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error {
require.Equal(t, l1Head, finalized)
return nil
}
// Allow the supervisor to promote the cross-safe L2 block to finalized.
seqMockBackend.FinalizedFn = func(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) {
return seq.SyncStatus().SafeL2.ID(), nil
}
// signal that L1 finalized; the cross-safe block we have should get finalized too
l1Miner.ActL1SafeNext(t)
l1Miner.ActL1FinalizeNext(t)
seq.ActL1SafeSignal(t)
seq.ActL1FinalizedSignal(t)
seq.ActL2PipelineFull(t)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.FinalizedL2.Number, "finalized the block")
h := actors.ChainA.SequencerEngine.L2Chain().CurrentSafeBlock().Hash()
require.Equal(t, head.Hash, h)
// Finalize L1, and see how the op-node forwards it to the supervisor.
// The supervisor then determines finality, which the op-node can use.
actors.L1Miner.ActL1SafeNext(t)
actors.L1Miner.ActL1FinalizeNext(t)
actors.ChainA.Sequencer.ActL1SafeSignal(t)
actors.ChainA.Sequencer.ActL1FinalizedSignal(t)
actors.ChainA.Sequencer.ActL2PipelineFull(t)
finalizedL2BlockID, err := actors.Supervisor.Finalized(t.Ctx(), actors.ChainA.ChainID)
require.NoError(t, err)
require.Equal(t, head, finalizedL2BlockID)
// The op-node needs a poke to look at the updated supervisor finality state
actors.ChainA.Sequencer.ActInteropBackendCheck(t)
actors.ChainA.Sequencer.ActL2PipelineFull(t)
h = actors.ChainA.SequencerEngine.L2Chain().CurrentFinalBlock().Hash()
require.Equal(t, head.Hash, h)
status = actors.ChainA.Sequencer.SyncStatus()
require.Equal(t, head, status.UnsafeL2.ID())
require.Equal(t, head, status.CrossUnsafeL2.ID())
require.Equal(t, head, status.LocalSafeL2.ID())
require.Equal(t, head, status.SafeL2.ID())
require.Equal(t, head, status.FinalizedL2.ID())
}
......@@ -74,7 +74,7 @@ func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eut
l1Cl, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard))
require.NoError(t, err)
engine := helpers.NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, helpers.EngineWithP2P())
engine := helpers.NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, jwtPath, helpers.EngineWithP2P())
l2EngineCl, err := sources.NewEngineClient(engine.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
......
......@@ -190,7 +190,7 @@ func aliceSimpleBlobTx(t helpers.Testing, dp *e2eutils.DeployParams) *types.Tran
func newEngine(t helpers.Testing, sd *e2eutils.SetupData, log log.Logger) *helpers.L2Engine {
jwtPath := e2eutils.WriteDefaultJWT(t)
return helpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
return helpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath)
}
// TestDencunBlobTxRPC tries to send a Blob tx to the L2 engine via RPC, it should not be accepted.
......
......@@ -439,3 +439,37 @@ func (su *SupervisorBackend) UpdateFinalizedL1(ctx context.Context, chainID type
return su.chainDBs.UpdateFinalizedL1(finalized)
}
// Access to synchronous processing for tests
// ----------------------------
func (su *SupervisorBackend) SyncEvents(chainID types.ChainID) error {
su.mu.RLock()
defer su.mu.RUnlock()
ch, ok := su.chainProcessors[chainID]
if !ok {
return types.ErrUnknownChain
}
ch.ProcessToHead()
return nil
}
func (su *SupervisorBackend) SyncCrossUnsafe(chainID types.ChainID) error {
su.mu.RLock()
defer su.mu.RUnlock()
ch, ok := su.crossUnsafeProcessors[chainID]
if !ok {
return types.ErrUnknownChain
}
return ch.ProcessWork()
}
func (su *SupervisorBackend) SyncCrossSafe(chainID types.ChainID) error {
su.mu.RLock()
defer su.mu.RUnlock()
ch, ok := su.crossSafeProcessors[chainID]
if !ok {
return types.ErrUnknownChain
}
return ch.ProcessWork()
}
......@@ -134,7 +134,7 @@ func (s *ChainProcessor) work() {
target := s.nextNum()
if err := s.update(target); err != nil {
if errors.Is(err, ethereum.NotFound) {
s.log.Info("Cannot find next block yet", "target", target)
s.log.Info("Cannot find next block yet", "target", target, "err", err)
} else if errors.Is(err, types.ErrNoRPCSource) {
s.log.Warn("No RPC source configured, cannot process new blocks")
} else {
......
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