Commit dee44ec6 authored by Axel Kingsley's avatar Axel Kingsley Committed by GitHub

Interop: Update Inputs (rebased) (#12204)

* op-node,op-supervisor: feed local-unsafe/local-safe/l1-finalized data to supervisor

* op-node,op-service,op-e2e: wip, fix interop op-node tests

* post-rebase compilation fixes

* BlockRef

* op-supervisor: fix service test, cleanup todo

* op-supervisor: link TODO comments to issue

* interop: fix e2e action test

---------
Co-authored-by: default avatarprotolambda <proto@protolambda.com>
No related merge requests found
package interop
import (
"context"
"testing"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"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.MockInteropBackend)(nil)
var _ interop.InteropBackend = (*testutils.FakeInteropBackend)(nil)
func TestInteropVerifier(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
......@@ -26,14 +29,14 @@ func TestInteropVerifier(gt *testing.T) {
// The state genesis in this test is pre-interop however.
sd.RollupCfg.InteropTime = new(uint64)
logger := testlog.Logger(t, log.LevelDebug)
seqMockBackend := &testutils.MockInteropBackend{}
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.MockInteropBackend{}
verMockBackend := &testutils.FakeInteropBackend{}
_, ver := helpers.SetupVerifier(t, sd, logger,
l1Miner.L1Client(t, sd.RollupCfg), l1Miner.BlobStore(), &sync.Config{},
helpers.WithInteropBackend(verMockBackend))
......@@ -42,12 +45,21 @@ func TestInteropVerifier(gt *testing.T) {
ver.ActL2PipelineFull(t)
l2ChainID := types.ChainIDFromBig(sd.RollupCfg.L2ChainID)
seqMockBackend.ExpectCheckBlock(l2ChainID, 1, types.LocalUnsafe, nil)
seqMockBackend.UpdateLocalUnsafeFn = func(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) 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)
seqMockBackend.AssertExpectations(t)
status := seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
require.Equal(t, uint64(0), status.CrossUnsafeL2.Number)
......@@ -56,10 +68,16 @@ func TestInteropVerifier(gt *testing.T) {
// promote it to cross-unsafe in the backend
// and see if the node picks up on it
seqMockBackend.ExpectCheckBlock(l2ChainID, 1, types.CrossUnsafe, 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)
out := unsafe
out.Cross = unsafe.Local
return out, nil
}
seq.ActInteropBackendCheck(t)
seq.ActL2PipelineFull(t)
seqMockBackend.AssertExpectations(t)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
require.Equal(t, uint64(1), status.CrossUnsafeL2.Number, "cross unsafe now")
......@@ -74,10 +92,20 @@ func TestInteropVerifier(gt *testing.T) {
l1Miner.ActL1EndBlock(t)
// Sync the L1 block, to verify the L2 block as local-safe.
seqMockBackend.ExpectCheckBlock(l2ChainID, 1, types.CrossUnsafe, nil) // not cross-safe yet
seqMockBackend.UpdateLocalUnsafeFn = nil
seqMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error {
require.Equal(t, uint64(1), lastDerived.Number)
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)
seqMockBackend.AssertExpectations(t)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
......@@ -86,10 +114,23 @@ func TestInteropVerifier(gt *testing.T) {
require.Equal(t, uint64(0), status.SafeL2.Number)
// Now mark it as cross-safe
seqMockBackend.ExpectCheckBlock(l2ChainID, 1, types.CrossSafe, nil)
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, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) {
require.Equal(t, uint64(1), blockNumber)
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)
seqMockBackend.AssertExpectations(t)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.UnsafeL2.Number)
......@@ -98,12 +139,30 @@ func TestInteropVerifier(gt *testing.T) {
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.L2BlockRef) error {
require.Equal(t, uint64(1), head.Number)
return nil
}
verMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error {
require.Equal(t, uint64(1), lastDerived.Number)
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.ExpectCheckBlock(l2ChainID, 1, types.LocalUnsafe, nil) // for the local unsafe check
verMockBackend.ExpectCheckBlock(l2ChainID, 1, types.LocalUnsafe, nil) // for the local safe check
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)
verMockBackend.AssertExpectations(t)
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")
......@@ -111,13 +170,16 @@ func TestInteropVerifier(gt *testing.T) {
require.Equal(t, uint64(0), status.SafeL2.Number, "not yet cross-safe")
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
}
// 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)
seqMockBackend.AssertExpectations(t)
status = seq.SyncStatus()
require.Equal(t, uint64(1), status.FinalizedL2.Number, "finalized the block")
......
......@@ -229,6 +229,22 @@ func (ev PromoteFinalizedEvent) String() string {
return "promote-finalized"
}
// FinalizedUpdateEvent signals that a block has been marked as finalized.
type FinalizedUpdateEvent struct {
Ref eth.L2BlockRef
}
func (ev FinalizedUpdateEvent) String() string {
return "finalized-update"
}
// RequestFinalizedUpdateEvent signals that a FinalizedUpdateEvent is needed.
type RequestFinalizedUpdateEvent struct{}
func (ev RequestFinalizedUpdateEvent) String() string {
return "request-finalized-update"
}
// CrossUpdateRequestEvent triggers update events to be emitted, repeating the current state.
type CrossUpdateRequestEvent struct {
CrossUnsafe bool
......@@ -419,8 +435,11 @@ func (d *EngDeriver) OnEvent(ev event.Event) bool {
return true
}
d.ec.SetFinalizedHead(x.Ref)
d.emitter.Emit(FinalizedUpdateEvent(x))
// Try to apply the forkchoice changes
d.emitter.Emit(TryUpdateEngineEvent{})
case RequestFinalizedUpdateEvent:
d.emitter.Emit(FinalizedUpdateEvent{Ref: d.ec.Finalized()})
case CrossUpdateRequestEvent:
if x.CrossUnsafe {
d.emitter.Emit(CrossUnsafeUpdateEvent{
......
......@@ -2,6 +2,7 @@ package interop
import (
"context"
"fmt"
"sync"
"time"
......@@ -11,19 +12,28 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/engine"
"github.com/ethereum-optimism/optimism/op-node/rollup/event"
"github.com/ethereum-optimism/optimism/op-node/rollup/finality"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
const checkBlockTimeout = time.Second * 10
const rpcTimeout = time.Second * 10
type InteropBackend interface {
CheckBlock(ctx context.Context,
chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (types.SafetyLevel, error)
UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error)
SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error)
Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error)
DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error)
UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error
UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error
UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error
}
type L2Source interface {
L2BlockRefByNumber(context.Context, uint64) (eth.L2BlockRef, error)
L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error)
}
// InteropDeriver watches for update events (either real changes to block safety,
......@@ -39,11 +49,6 @@ type InteropDeriver struct {
driverCtx context.Context
// L2 blockhash -> derived from L1 block ref.
// Added to when a block is local-safe.
// Removed from when it is promoted to cross-safe.
derivedFrom map[common.Hash]eth.L1BlockRef
backend InteropBackend
l2 L2Source
......@@ -58,13 +63,12 @@ var _ event.AttachEmitter = (*InteropDeriver)(nil)
func NewInteropDeriver(log log.Logger, cfg *rollup.Config,
driverCtx context.Context, backend InteropBackend, l2 L2Source) *InteropDeriver {
return &InteropDeriver{
log: log,
cfg: cfg,
chainID: types.ChainIDFromBig(cfg.L2ChainID),
driverCtx: driverCtx,
derivedFrom: make(map[common.Hash]eth.L1BlockRef),
backend: backend,
l2: l2,
log: log,
cfg: cfg,
chainID: types.ChainIDFromBig(cfg.L2ChainID),
driverCtx: driverCtx,
backend: backend,
l2: l2,
}
}
......@@ -78,87 +82,178 @@ func (d *InteropDeriver) OnEvent(ev event.Event) bool {
switch x := ev.(type) {
case engine.UnsafeUpdateEvent:
d.emitter.Emit(engine.RequestCrossUnsafeEvent{})
d.onLocalUnsafeUpdate(x)
case engine.LocalSafeUpdateEvent:
d.onLocalSafeUpdate(x)
case finality.FinalizeL1Event:
d.onFinalizedL1(x)
case engine.CrossUnsafeUpdateEvent:
if x.CrossUnsafe.Number >= x.LocalUnsafe.Number {
break // nothing left to promote
}
// Pre-interop the engine itself handles promotion to cross-unsafe.
// Check if the next block (still unsafe) can be promoted to cross-unsafe.
if !d.cfg.IsInterop(d.cfg.TimestampForBlock(x.CrossUnsafe.Number + 1)) {
return false
}
ctx, cancel := context.WithTimeout(d.driverCtx, checkBlockTimeout)
defer cancel()
candidate, err := d.l2.L2BlockRefByNumber(ctx, x.CrossUnsafe.Number+1)
if err != nil {
d.log.Warn("Failed to fetch next cross-unsafe candidate", "err", err)
break
}
blockSafety, err := d.backend.CheckBlock(ctx, d.chainID, candidate.Hash, candidate.Number)
if err != nil {
d.log.Warn("Failed to check interop safety of unsafe block", "err", err)
break
if err := d.onCrossUnsafe(x); err != nil {
d.log.Error("Failed to process cross-unsafe update", "err", err)
}
switch blockSafety {
case types.CrossUnsafe, types.CrossSafe, types.Finalized:
// Hold off on promoting higher than cross-unsafe,
// this will happen once we verify it to be local-safe first.
d.emitter.Emit(engine.PromoteCrossUnsafeEvent{Ref: candidate})
}
case engine.LocalSafeUpdateEvent:
d.log.Debug("Local safe update event", "block", x.Ref.Hash, "derivedFrom", x.DerivedFrom)
d.derivedFrom[x.Ref.Hash] = x.DerivedFrom
d.emitter.Emit(engine.RequestCrossSafeEvent{})
case engine.CrossSafeUpdateEvent:
if x.CrossSafe.Number >= x.LocalSafe.Number {
break // nothing left to promote
}
// Pre-interop the engine itself handles promotion to cross-safe.
// Check if the next block (not yet cross-safe) can be promoted to cross-safe.
if !d.cfg.IsInterop(d.cfg.TimestampForBlock(x.CrossSafe.Number + 1)) {
return false
}
ctx, cancel := context.WithTimeout(d.driverCtx, checkBlockTimeout)
defer cancel()
candidate, err := d.l2.L2BlockRefByNumber(ctx, x.CrossSafe.Number+1)
if err != nil {
d.log.Warn("Failed to fetch next cross-safe candidate", "err", err)
break
}
blockSafety, err := d.backend.CheckBlock(ctx, d.chainID, candidate.Hash, candidate.Number)
if err != nil {
d.log.Warn("Failed to check interop safety of local-safe block", "err", err)
break
}
derivedFrom, ok := d.derivedFrom[candidate.Hash]
if !ok {
d.log.Warn("Unknown block candidate source, cannot promote block safety", "block", candidate, "safety", blockSafety)
break
if err := d.onCrossSafeUpdateEvent(x); err != nil {
d.log.Error("Failed to process cross-safe update", "err", err)
}
switch blockSafety {
case types.CrossSafe:
d.log.Info("Verified cross-safe block", "block", candidate, "derivedFrom", derivedFrom)
// TODO(#11673): once we have interop reorg support, we need to clean stale blocks also.
delete(d.derivedFrom, candidate.Hash)
d.emitter.Emit(engine.PromoteSafeEvent{
Ref: candidate,
DerivedFrom: derivedFrom,
})
case types.Finalized:
// TODO(#11673): once we have interop reorg support, we need to clean stale blocks also.
delete(d.derivedFrom, candidate.Hash)
d.emitter.Emit(engine.PromoteSafeEvent{
Ref: candidate,
DerivedFrom: derivedFrom,
})
d.emitter.Emit(engine.PromoteFinalizedEvent{
Ref: candidate,
})
case engine.FinalizedUpdateEvent:
if err := d.onFinalizedUpdate(x); err != nil {
d.log.Error("Failed to process finalized update", "err", err)
}
// no reorg support yet; the safe L2 head will finalize eventually, no exceptions
default:
return false
}
return true
}
func (d *InteropDeriver) onLocalUnsafeUpdate(x engine.UnsafeUpdateEvent) {
d.log.Debug("Signaling unsafe L2 head update to interop backend", "head", x.Ref)
ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout)
defer cancel()
if err := d.backend.UpdateLocalUnsafe(ctx, d.chainID, x.Ref); err != nil {
d.log.Warn("Failed to signal unsafe L2 head to interop backend", "head", x.Ref, "err", err)
// still continue to try and do a cross-unsafe update
}
// Now that the op-supervisor is aware of the new local-unsafe block, we want to check if cross-unsafe changed.
d.emitter.Emit(engine.RequestCrossUnsafeEvent{})
}
func (d *InteropDeriver) onLocalSafeUpdate(x engine.LocalSafeUpdateEvent) {
d.log.Debug("Signaling derived-from update to interop backend", "derivedFrom", x.DerivedFrom, "block", x.Ref)
ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout)
defer cancel()
if err := d.backend.UpdateLocalSafe(ctx, d.chainID, x.DerivedFrom, x.Ref); err != nil {
d.log.Debug("Failed to signal derived-from update to interop backend", "derivedFrom", x.DerivedFrom, "block", x.Ref)
// still continue to try and do a cross-safe update
}
// Now that the op-supervisor is aware of the new local-safe block, we want to check if cross-safe changed.
d.emitter.Emit(engine.RequestCrossSafeEvent{})
}
func (d *InteropDeriver) onFinalizedL1(x finality.FinalizeL1Event) {
if !d.cfg.IsInterop(x.FinalizedL1.Time) {
return
}
d.log.Debug("Signaling finalized L1 update to interop backend", "finalized", x.FinalizedL1)
ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout)
defer cancel()
if err := d.backend.UpdateFinalizedL1(ctx, d.chainID, x.FinalizedL1); err != nil {
d.log.Warn("Failed to signal finalized L1 block to interop backend", "finalized", x.FinalizedL1, "err", err)
}
// New L2 blocks may be ready to finalize now that the backend knows of new L1 finalized info.
d.emitter.Emit(engine.RequestFinalizedUpdateEvent{})
}
func (d *InteropDeriver) onCrossUnsafe(x engine.CrossUnsafeUpdateEvent) error {
if x.CrossUnsafe.Number >= x.LocalUnsafe.Number {
return nil // nothing left to promote
}
// Pre-interop the engine itself handles promotion to cross-unsafe.
// Start checking cross-unsafe once the local-unsafe block is in the interop update.
if !d.cfg.IsInterop(x.LocalUnsafe.Time) {
return nil
}
ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout)
defer cancel()
view := types.ReferenceView{
Local: x.LocalUnsafe.ID(),
Cross: x.CrossUnsafe.ID(),
}
result, err := d.backend.UnsafeView(ctx, d.chainID, view)
if err != nil {
return fmt.Errorf("failed to check unsafe-level view: %w", err)
}
if result.Cross.Number == x.CrossUnsafe.Number {
// supervisor is in sync with op-node
return nil
}
if result.Cross.Number < x.CrossUnsafe.Number {
d.log.Warn("op-supervisor is behind known cross-unsafe block", "supervisor", result.Cross, "known", x.CrossUnsafe)
return nil
}
d.log.Info("New cross-unsafe block", "block", result.Cross.Number)
// Note: in the future we want to do reorg-checks,
// and initiate a reorg, if found to be on a conflicting chain.
ref, err := d.l2.L2BlockRefByHash(ctx, result.Cross.Hash)
if err != nil {
return fmt.Errorf("failed to get cross-unsafe block info of %s: %w", result.Cross, err)
}
d.emitter.Emit(engine.PromoteCrossUnsafeEvent{Ref: ref})
return nil
}
func (d *InteropDeriver) onCrossSafeUpdateEvent(x engine.CrossSafeUpdateEvent) error {
if x.CrossSafe.Number >= x.LocalSafe.Number {
return nil // nothing left to promote
}
// Pre-interop the engine itself handles promotion to cross-safe.
// Start checking cross-safe once the local-safe block is in the interop update.
if !d.cfg.IsInterop(x.LocalSafe.Time) {
return nil
}
ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout)
defer cancel()
view := types.ReferenceView{
Local: x.LocalSafe.ID(),
Cross: x.CrossSafe.ID(),
}
result, err := d.backend.SafeView(ctx, d.chainID, view)
if err != nil {
return fmt.Errorf("failed to check safe-level view: %w", err)
}
if result.Cross.Number == x.CrossSafe.Number {
// supervisor is in sync with op-node
return nil
}
if result.Cross.Number < x.CrossSafe.Number {
d.log.Warn("op-supervisor is behind known cross-safe block", "supervisor", result.Cross, "known", x.CrossSafe)
// TODO: we may want to force set the cross-safe block in the engine,
// and then reset derivation, so this op-node can help get the supervisor back in sync.
return nil
}
derivedFrom, err := d.backend.DerivedFrom(ctx, d.chainID, result.Cross.Hash, result.Cross.Number)
if err != nil {
return fmt.Errorf("failed to get derived-from of %s: %w", result.Cross, err)
}
ref, err := d.l2.L2BlockRefByHash(ctx, result.Cross.Hash)
if err != nil {
return fmt.Errorf("failed to get block ref of %s: %w", result.Cross, err)
}
d.emitter.Emit(engine.PromoteSafeEvent{
Ref: ref,
DerivedFrom: derivedFrom,
})
d.emitter.Emit(engine.RequestFinalizedUpdateEvent{})
return nil
}
func (d *InteropDeriver) onFinalizedUpdate(x engine.FinalizedUpdateEvent) error {
// Note: we have to check interop fork, but finality may be pre-fork activation until we update.
// We may want to change this to only start checking finality once the local head is past the activation.
ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout)
defer cancel()
finalized, err := d.backend.Finalized(ctx, d.chainID)
if err != nil {
return fmt.Errorf("failed to retrieve finalized L2 block from supervisor: %w", err)
}
// Check if we can finalize something new
if finalized.Number == x.Ref.Number {
// supervisor is in sync with op-node
return nil
}
if finalized.Number < x.Ref.Number {
d.log.Warn("op-supervisor is behind known finalized block", "supervisor", finalized, "known", x.Ref)
return nil
}
ref, err := d.l2.L2BlockRefByHash(ctx, finalized.Hash)
if err != nil {
return fmt.Errorf("failed to get block ref of %s: %w", finalized, err)
}
d.emitter.Emit(engine.PromoteFinalizedEvent{
Ref: ref,
})
return nil
}
......@@ -6,17 +6,18 @@ import (
"math/rand" // nosemgrep
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/engine"
"github.com/ethereum-optimism/optimism/op-node/rollup/finality"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils"
supervisortypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
var _ InteropBackend = (*testutils.MockInteropBackend)(nil)
func TestInteropDeriver(t *testing.T) {
logger := testlog.Logger(t, log.LevelInfo)
l2Source := &testutils.MockL2Client{}
......@@ -31,25 +32,34 @@ func TestInteropDeriver(t *testing.T) {
interopDeriver.AttachEmitter(emitter)
rng := rand.New(rand.NewSource(123))
t.Run("unsafe blocks trigger cross-unsafe check attempts", func(t *testing.T) {
t.Run("local-unsafe blocks push to supervisor and trigger cross-unsafe attempts", func(t *testing.T) {
emitter.ExpectOnce(engine.RequestCrossUnsafeEvent{})
interopDeriver.OnEvent(engine.UnsafeUpdateEvent{
Ref: testutils.RandomL2BlockRef(rng),
})
unsafeHead := testutils.RandomL2BlockRef(rng)
interopBackend.ExpectUpdateLocalUnsafe(chainID, unsafeHead, nil)
interopDeriver.OnEvent(engine.UnsafeUpdateEvent{Ref: unsafeHead})
emitter.AssertExpectations(t)
interopBackend.AssertExpectations(t)
})
t.Run("establish cross-unsafe", func(t *testing.T) {
crossUnsafe := testutils.RandomL2BlockRef(rng)
firstLocalUnsafe := testutils.NextRandomL2Ref(rng, 2, crossUnsafe, crossUnsafe.L1Origin)
lastLocalUnsafe := testutils.NextRandomL2Ref(rng, 2, firstLocalUnsafe, firstLocalUnsafe.L1Origin)
interopBackend.ExpectCheckBlock(
chainID, firstLocalUnsafe.Number, supervisortypes.CrossUnsafe, nil)
oldCrossUnsafe := testutils.RandomL2BlockRef(rng)
nextCrossUnsafe := testutils.NextRandomL2Ref(rng, 2, oldCrossUnsafe, oldCrossUnsafe.L1Origin)
lastLocalUnsafe := testutils.NextRandomL2Ref(rng, 2, nextCrossUnsafe, nextCrossUnsafe.L1Origin)
localView := supervisortypes.ReferenceView{
Local: lastLocalUnsafe.ID(),
Cross: oldCrossUnsafe.ID(),
}
supervisorView := supervisortypes.ReferenceView{
Local: lastLocalUnsafe.ID(),
Cross: nextCrossUnsafe.ID(),
}
interopBackend.ExpectUnsafeView(
chainID, localView, supervisorView, nil)
l2Source.ExpectL2BlockRefByHash(nextCrossUnsafe.Hash, nextCrossUnsafe, nil)
emitter.ExpectOnce(engine.PromoteCrossUnsafeEvent{
Ref: firstLocalUnsafe,
Ref: nextCrossUnsafe,
})
l2Source.ExpectL2BlockRefByNumber(firstLocalUnsafe.Number, firstLocalUnsafe, nil)
interopDeriver.OnEvent(engine.CrossUnsafeUpdateEvent{
CrossUnsafe: crossUnsafe,
CrossUnsafe: oldCrossUnsafe,
LocalUnsafe: lastLocalUnsafe,
})
interopBackend.AssertExpectations(t)
......@@ -57,53 +67,62 @@ func TestInteropDeriver(t *testing.T) {
l2Source.AssertExpectations(t)
})
t.Run("deny cross-unsafe", func(t *testing.T) {
crossUnsafe := testutils.RandomL2BlockRef(rng)
firstLocalUnsafe := testutils.NextRandomL2Ref(rng, 2, crossUnsafe, crossUnsafe.L1Origin)
lastLocalUnsafe := testutils.NextRandomL2Ref(rng, 2, firstLocalUnsafe, firstLocalUnsafe.L1Origin)
interopBackend.ExpectCheckBlock(
chainID, firstLocalUnsafe.Number, supervisortypes.LocalUnsafe, nil)
l2Source.ExpectL2BlockRefByNumber(firstLocalUnsafe.Number, firstLocalUnsafe, nil)
oldCrossUnsafe := testutils.RandomL2BlockRef(rng)
nextCrossUnsafe := testutils.NextRandomL2Ref(rng, 2, oldCrossUnsafe, oldCrossUnsafe.L1Origin)
lastLocalUnsafe := testutils.NextRandomL2Ref(rng, 2, nextCrossUnsafe, nextCrossUnsafe.L1Origin)
localView := supervisortypes.ReferenceView{
Local: lastLocalUnsafe.ID(),
Cross: oldCrossUnsafe.ID(),
}
supervisorView := supervisortypes.ReferenceView{
Local: lastLocalUnsafe.ID(),
Cross: oldCrossUnsafe.ID(), // stuck on same cross-safe
}
interopBackend.ExpectUnsafeView(
chainID, localView, supervisorView, nil)
interopDeriver.OnEvent(engine.CrossUnsafeUpdateEvent{
CrossUnsafe: crossUnsafe,
CrossUnsafe: oldCrossUnsafe,
LocalUnsafe: lastLocalUnsafe,
})
interopBackend.AssertExpectations(t)
// no cross-unsafe promote event is expected
emitter.AssertExpectations(t)
emitter.AssertExpectations(t) // no promote-cross-unsafe event expected
l2Source.AssertExpectations(t)
})
t.Run("register local-safe", func(t *testing.T) {
t.Run("local-safe blocks push to supervisor and trigger cross-safe attempts", func(t *testing.T) {
emitter.ExpectOnce(engine.RequestCrossSafeEvent{})
derivedFrom := testutils.RandomBlockRef(rng)
localSafe := testutils.RandomL2BlockRef(rng)
emitter.ExpectOnce(engine.RequestCrossSafeEvent{})
interopBackend.ExpectUpdateLocalSafe(chainID, derivedFrom, localSafe, nil)
interopDeriver.OnEvent(engine.LocalSafeUpdateEvent{
Ref: localSafe,
DerivedFrom: derivedFrom,
})
require.Contains(t, interopDeriver.derivedFrom, localSafe.Hash)
require.Equal(t, derivedFrom, interopDeriver.derivedFrom[localSafe.Hash])
emitter.AssertExpectations(t)
interopBackend.AssertExpectations(t)
})
t.Run("establish cross-safe", func(t *testing.T) {
derivedFrom := testutils.RandomBlockRef(rng)
crossSafe := testutils.RandomL2BlockRef(rng)
firstLocalSafe := testutils.NextRandomL2Ref(rng, 2, crossSafe, crossSafe.L1Origin)
lastLocalSafe := testutils.NextRandomL2Ref(rng, 2, firstLocalSafe, firstLocalSafe.L1Origin)
emitter.ExpectOnce(engine.RequestCrossSafeEvent{})
// The local safe block must be known, for the derived-from mapping to work
interopDeriver.OnEvent(engine.LocalSafeUpdateEvent{
Ref: firstLocalSafe,
DerivedFrom: derivedFrom,
})
interopBackend.ExpectCheckBlock(
chainID, firstLocalSafe.Number, supervisortypes.CrossSafe, nil)
oldCrossSafe := testutils.RandomL2BlockRef(rng)
nextCrossSafe := testutils.NextRandomL2Ref(rng, 2, oldCrossSafe, oldCrossSafe.L1Origin)
lastLocalSafe := testutils.NextRandomL2Ref(rng, 2, nextCrossSafe, nextCrossSafe.L1Origin)
localView := supervisortypes.ReferenceView{
Local: lastLocalSafe.ID(),
Cross: oldCrossSafe.ID(),
}
supervisorView := supervisortypes.ReferenceView{
Local: lastLocalSafe.ID(),
Cross: nextCrossSafe.ID(),
}
interopBackend.ExpectSafeView(chainID, localView, supervisorView, nil)
interopBackend.ExpectDerivedFrom(chainID, nextCrossSafe.Hash, nextCrossSafe.Number, derivedFrom, nil)
l2Source.ExpectL2BlockRefByHash(nextCrossSafe.Hash, nextCrossSafe, nil)
emitter.ExpectOnce(engine.PromoteSafeEvent{
Ref: firstLocalSafe,
Ref: nextCrossSafe,
DerivedFrom: derivedFrom,
})
l2Source.ExpectL2BlockRefByNumber(firstLocalSafe.Number, firstLocalSafe, nil)
emitter.ExpectOnce(engine.RequestFinalizedUpdateEvent{})
interopDeriver.OnEvent(engine.CrossSafeUpdateEvent{
CrossSafe: crossSafe,
CrossSafe: oldCrossSafe,
LocalSafe: lastLocalSafe,
})
interopBackend.AssertExpectations(t)
......@@ -111,26 +130,54 @@ func TestInteropDeriver(t *testing.T) {
l2Source.AssertExpectations(t)
})
t.Run("deny cross-safe", func(t *testing.T) {
derivedFrom := testutils.RandomBlockRef(rng)
crossSafe := testutils.RandomL2BlockRef(rng)
firstLocalSafe := testutils.NextRandomL2Ref(rng, 2, crossSafe, crossSafe.L1Origin)
lastLocalSafe := testutils.NextRandomL2Ref(rng, 2, firstLocalSafe, firstLocalSafe.L1Origin)
emitter.ExpectOnce(engine.RequestCrossSafeEvent{})
// The local safe block must be known, for the derived-from mapping to work
interopDeriver.OnEvent(engine.LocalSafeUpdateEvent{
Ref: firstLocalSafe,
DerivedFrom: derivedFrom,
})
interopBackend.ExpectCheckBlock(
chainID, firstLocalSafe.Number, supervisortypes.LocalSafe, nil)
l2Source.ExpectL2BlockRefByNumber(firstLocalSafe.Number, firstLocalSafe, nil)
oldCrossSafe := testutils.RandomL2BlockRef(rng)
nextCrossSafe := testutils.NextRandomL2Ref(rng, 2, oldCrossSafe, oldCrossSafe.L1Origin)
lastLocalSafe := testutils.NextRandomL2Ref(rng, 2, nextCrossSafe, nextCrossSafe.L1Origin)
localView := supervisortypes.ReferenceView{
Local: lastLocalSafe.ID(),
Cross: oldCrossSafe.ID(),
}
supervisorView := supervisortypes.ReferenceView{
Local: lastLocalSafe.ID(),
Cross: oldCrossSafe.ID(), // stay on old cross-safe
}
interopBackend.ExpectSafeView(chainID, localView, supervisorView, nil)
interopDeriver.OnEvent(engine.CrossSafeUpdateEvent{
CrossSafe: crossSafe,
CrossSafe: oldCrossSafe,
LocalSafe: lastLocalSafe,
})
interopBackend.AssertExpectations(t)
// no cross-safe promote event is expected
emitter.AssertExpectations(t)
emitter.AssertExpectations(t) // no promote-cross-safe event expected
l2Source.AssertExpectations(t)
})
t.Run("finalized L1 trigger cross-L2 finality check", func(t *testing.T) {
emitter.ExpectOnce(engine.RequestFinalizedUpdateEvent{})
finalizedL1 := testutils.RandomBlockRef(rng)
interopBackend.ExpectUpdateFinalizedL1(chainID, finalizedL1, nil)
interopDeriver.OnEvent(finality.FinalizeL1Event{
FinalizedL1: finalizedL1,
})
emitter.AssertExpectations(t)
interopBackend.AssertExpectations(t)
})
t.Run("next L2 finalized block", func(t *testing.T) {
oldFinalizedL2 := testutils.RandomL2BlockRef(rng)
intermediateL2 := testutils.NextRandomL2Ref(rng, 2, oldFinalizedL2, oldFinalizedL2.L1Origin)
nextFinalizedL2 := testutils.NextRandomL2Ref(rng, 2, intermediateL2, intermediateL2.L1Origin)
emitter.ExpectOnce(engine.PromoteFinalizedEvent{
Ref: nextFinalizedL2,
})
interopBackend.ExpectFinalized(chainID, nextFinalizedL2.ID(), nil)
l2Source.ExpectL2BlockRefByHash(nextFinalizedL2.Hash, nextFinalizedL2, nil)
interopDeriver.OnEvent(engine.FinalizedUpdateEvent{Ref: oldFinalizedL2})
emitter.AssertExpectations(t)
interopBackend.AssertExpectations(t)
})
t.Run("keep L2 finalized block", func(t *testing.T) {
oldFinalizedL2 := testutils.RandomL2BlockRef(rng)
interopBackend.ExpectFinalized(chainID, oldFinalizedL2.ID(), nil)
interopDeriver.OnEvent(engine.FinalizedUpdateEvent{Ref: oldFinalizedL2})
emitter.AssertExpectations(t) // no PromoteFinalizedEvent
interopBackend.AssertExpectations(t)
})
}
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -65,20 +66,48 @@ func (cl *SupervisorClient) AddL2RPC(
return result
}
func (cl *SupervisorClient) CheckBlock(ctx context.Context,
chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (types.SafetyLevel, error) {
var result types.SafetyLevel
err := cl.client.CallContext(
ctx,
&result,
"supervisor_checkBlock",
(*hexutil.U256)(&chainID), blockHash, hexutil.Uint64(blockNumber))
func (cl *SupervisorClient) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) {
var result types.ReferenceView
err := cl.client.CallContext(ctx, &result, "supervisor_unsafeView", (*hexutil.U256)(&chainID), unsafe)
if err != nil {
return types.LocalUnsafe, fmt.Errorf("failed to check Block %s:%d (chain %s): %w", blockHash, blockNumber, chainID, err)
return types.ReferenceView{}, fmt.Errorf("failed to share unsafe block view %s (chain %s): %w", unsafe, chainID, err)
}
return result, nil
}
func (cl *SupervisorClient) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) {
var result types.ReferenceView
err := cl.client.CallContext(ctx, &result, "supervisor_safeView", (*hexutil.U256)(&chainID), safe)
if err != nil {
return types.ReferenceView{}, fmt.Errorf("failed to share safe block view %s (chain %s): %w", safe, chainID, err)
}
return result, nil
}
func (cl *SupervisorClient) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) {
var result eth.BlockID
err := cl.client.CallContext(ctx, &result, "supervisor_finalized", chainID)
return result, err
}
func (cl *SupervisorClient) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) {
var result eth.L1BlockRef
err := cl.client.CallContext(ctx, &result, "supervisor_derivedFrom", chainID, blockHash, blockNumber)
return result, err
}
func (cl *SupervisorClient) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error {
return cl.client.CallContext(ctx, nil, "supervisor_updateLocalUnsafe", chainID, head)
}
func (cl *SupervisorClient) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error {
return cl.client.CallContext(ctx, nil, "supervisor_updateLocalSafe", chainID, derivedFrom, lastDerived)
}
func (cl *SupervisorClient) UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalizedL1 eth.L1BlockRef) error {
return cl.client.CallContext(ctx, nil, "supervisor_updateFinalizedL1", chainID, finalizedL1)
}
func (cl *SupervisorClient) Close() {
cl.client.Close()
}
package testutils
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
type FakeInteropBackend struct {
UnsafeViewFn func(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error)
SafeViewFn func(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error)
FinalizedFn func(ctx context.Context, chainID types.ChainID) (eth.BlockID, error)
DerivedFromFn func(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error)
UpdateLocalUnsafeFn func(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error
UpdateLocalSafeFn func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error
UpdateFinalizedL1Fn func(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error
}
func (m *FakeInteropBackend) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) {
return m.UnsafeViewFn(ctx, chainID, unsafe)
}
func (m *FakeInteropBackend) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) {
return m.SafeViewFn(ctx, chainID, safe)
}
func (m *FakeInteropBackend) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) {
return m.FinalizedFn(ctx, chainID)
}
func (m *FakeInteropBackend) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) {
return m.DerivedFromFn(ctx, chainID, blockHash, blockNumber)
}
func (m *FakeInteropBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error {
return m.UpdateLocalUnsafeFn(ctx, chainID, head)
}
func (m *FakeInteropBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error {
return m.UpdateLocalSafeFn(ctx, chainID, derivedFrom, lastDerived)
}
func (m *FakeInteropBackend) UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error {
return m.UpdateFinalizedL1Fn(ctx, chainID, finalized)
}
......@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -14,13 +15,89 @@ type MockInteropBackend struct {
Mock mock.Mock
}
func (m *MockInteropBackend) ExpectCheckBlock(chainID types.ChainID, blockNumber uint64, safety types.SafetyLevel, err error) {
m.Mock.On("CheckBlock", chainID, blockNumber).Once().Return(safety, &err)
func (m *MockInteropBackend) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) {
result := m.Mock.MethodCalled("UnsafeView", chainID, unsafe)
return result.Get(0).(types.ReferenceView), *result.Get(1).(*error)
}
func (m *MockInteropBackend) CheckBlock(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (types.SafetyLevel, error) {
result := m.Mock.MethodCalled("CheckBlock", chainID, blockNumber)
return result.Get(0).(types.SafetyLevel), *result.Get(1).(*error)
func (m *MockInteropBackend) ExpectUnsafeView(chainID types.ChainID, unsafe types.ReferenceView, result types.ReferenceView, err error) {
m.Mock.On("UnsafeView", chainID, unsafe).Once().Return(result, &err)
}
func (m *MockInteropBackend) OnUnsafeView(chainID types.ChainID, fn func(request types.ReferenceView) (result types.ReferenceView, err error)) {
var result types.ReferenceView
var err error
m.Mock.On("UnsafeView", chainID, mock.Anything).Run(func(args mock.Arguments) {
v := args[0].(types.ReferenceView)
result, err = fn(v)
}).Return(result, &err)
}
func (m *MockInteropBackend) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) {
result := m.Mock.MethodCalled("SafeView", chainID, safe)
return result.Get(0).(types.ReferenceView), *result.Get(1).(*error)
}
func (m *MockInteropBackend) ExpectSafeView(chainID types.ChainID, safe types.ReferenceView, result types.ReferenceView, err error) {
m.Mock.On("SafeView", chainID, safe).Once().Return(result, &err)
}
func (m *MockInteropBackend) OnSafeView(chainID types.ChainID, fn func(request types.ReferenceView) (result types.ReferenceView, err error)) {
var result types.ReferenceView
var err error
m.Mock.On("SafeView", chainID, mock.Anything).Run(func(args mock.Arguments) {
v := args[0].(types.ReferenceView)
result, err = fn(v)
}).Return(result, &err)
}
func (m *MockInteropBackend) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) {
result := m.Mock.MethodCalled("Finalized", chainID)
return result.Get(0).(eth.BlockID), *result.Get(1).(*error)
}
func (m *MockInteropBackend) ExpectFinalized(chainID types.ChainID, result eth.BlockID, err error) {
m.Mock.On("Finalized", chainID).Once().Return(result, &err)
}
func (m *MockInteropBackend) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) {
result := m.Mock.MethodCalled("DerivedFrom", chainID, blockHash, blockNumber)
return result.Get(0).(eth.L1BlockRef), *result.Get(1).(*error)
}
func (m *MockInteropBackend) ExpectDerivedFrom(chainID types.ChainID, blockHash common.Hash, blockNumber uint64, result eth.L1BlockRef, err error) {
m.Mock.On("DerivedFrom", chainID, blockHash, blockNumber).Once().Return(result, &err)
}
func (m *MockInteropBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error {
result := m.Mock.MethodCalled("UpdateLocalUnsafe", chainID, head)
return *result.Get(0).(*error)
}
func (m *MockInteropBackend) ExpectUpdateLocalUnsafe(chainID types.ChainID, head eth.L2BlockRef, err error) {
m.Mock.On("UpdateLocalUnsafe", chainID, head).Once().Return(&err)
}
func (m *MockInteropBackend) ExpectAnyUpdateLocalUnsafe(chainID types.ChainID, err error) {
m.Mock.On("UpdateLocalUnsafe", chainID, mock.Anything).Once().Return(&err)
}
func (m *MockInteropBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error {
result := m.Mock.MethodCalled("UpdateLocalSafe", chainID, derivedFrom, lastDerived)
return *result.Get(0).(*error)
}
func (m *MockInteropBackend) ExpectUpdateLocalSafe(chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef, err error) {
m.Mock.On("UpdateLocalSafe", chainID, derivedFrom, lastDerived).Once().Return(&err)
}
func (m *MockInteropBackend) UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error {
result := m.Mock.MethodCalled("UpdateFinalizedL1", chainID, finalized)
return *result.Get(0).(*error)
}
func (m *MockInteropBackend) ExpectUpdateFinalizedL1(chainID types.ChainID, finalized eth.L1BlockRef, err error) {
m.Mock.On("UpdateFinalizedL1", chainID, finalized).Once().Return(&err)
}
func (m *MockInteropBackend) AssertExpectations(t mock.TestingT) {
......
......@@ -226,3 +226,12 @@ func (su *SupervisorBackend) CheckBlock(chainID *hexutil.U256, blockHash common.
safest := su.db.Safest(types.ChainID(*chainID), uint64(blockNumber), 0)
return safest, nil
}
func (su *SupervisorBackend) DerivedFrom(
ctx context.Context,
chainID types.ChainID,
blockHash common.Hash,
blockNumber uint64) (eth.BlockRef, error) {
// TODO(#12358): attach to backend
return eth.BlockRef{}, nil
}
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -55,6 +56,10 @@ func (m *MockBackend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, b
return types.CrossUnsafe, nil
}
func (m *MockBackend) DerivedFrom(ctx context.Context, t types.ChainID, parentHash common.Hash, n uint64) (eth.BlockRef, error) {
return eth.BlockRef{}, nil
}
func (m *MockBackend) Close() error {
return nil
}
......@@ -6,6 +6,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -19,6 +20,13 @@ type QueryBackend interface {
CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error)
CheckMessages(messages []types.Message, minSafety types.SafetyLevel) error
CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error)
DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.BlockRef, error)
}
type UpdatesBackend interface {
UpdateLocalUnsafe(chainID types.ChainID, head eth.BlockRef)
UpdateLocalSafe(chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef)
UpdateFinalizedL1(chainID types.ChainID, finalized eth.BlockRef)
}
type Backend interface {
......@@ -44,9 +52,24 @@ func (q *QueryFrontend) CheckMessages(
return q.Supervisor.CheckMessages(messages, minSafety)
}
// CheckBlock checks the safety-level of an L2 block as a whole.
func (q *QueryFrontend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) {
return q.Supervisor.CheckBlock(chainID, blockHash, blockNumber)
func (q *QueryFrontend) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) {
// TODO(#12358): attach to backend
return types.ReferenceView{}, nil
}
func (q *QueryFrontend) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) {
// TODO(#12358): attach to backend
return types.ReferenceView{}, nil
}
func (q *QueryFrontend) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) {
// TODO(#12358): attach to backend
return eth.BlockID{}, nil
}
func (q *QueryFrontend) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.BlockRef, error) {
// TODO(#12358): attach to backend
return eth.BlockRef{}, nil
}
type AdminFrontend struct {
......@@ -67,3 +90,19 @@ func (a *AdminFrontend) Stop(ctx context.Context) error {
func (a *AdminFrontend) AddL2RPC(ctx context.Context, rpc string) error {
return a.Supervisor.AddL2RPC(ctx, rpc)
}
type UpdatesFrontend struct {
Supervisor UpdatesBackend
}
func (u *UpdatesFrontend) UpdateLocalUnsafe(chainID types.ChainID, head eth.BlockRef) {
u.Supervisor.UpdateLocalUnsafe(chainID, head)
}
func (u *UpdatesFrontend) UpdateLocalSafe(chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) {
u.Supervisor.UpdateLocalSafe(chainID, derivedFrom, lastDerived)
}
func (u *UpdatesFrontend) UpdateFinalizedL1(chainID types.ChainID, finalized eth.BlockRef) {
u.Supervisor.UpdateFinalizedL1(chainID, finalized)
}
......@@ -5,12 +5,9 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/dial"
......@@ -19,6 +16,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -62,8 +60,14 @@ func TestSupervisorService(t *testing.T) {
require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
var dest types.SafetyLevel
err = cl.CallContext(ctx, &dest, "supervisor_checkBlock",
(*hexutil.U256)(uint256.NewInt(1)), common.Hash{0xab}, hexutil.Uint64(123))
err = cl.CallContext(ctx, &dest, "supervisor_checkMessage",
types.Identifier{
Origin: common.Address{0xaa},
BlockNumber: 123,
LogIndex: 42,
Timestamp: 1234567,
ChainID: types.ChainID{0xbb},
}, common.Hash{0xcc})
cancel()
require.NoError(t, err)
require.Equal(t, types.CrossUnsafe, dest, "expecting mock to return cross-unsafe")
......
......@@ -11,6 +11,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
type ExecutingMessage struct {
......@@ -160,3 +162,12 @@ func (id ChainID) ToUInt32() (uint32, error) {
}
return uint32(v64), nil
}
type ReferenceView struct {
Local eth.BlockID `json:"local"`
Cross eth.BlockID `json:"cross"`
}
func (v ReferenceView) String() string {
return fmt.Sprintf("View(local: %s, cross: %s)", v.Local, v.Cross)
}
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