safe_update.go 5.89 KB
Newer Older
1 2 3 4 5 6 7 8
package cross

import (
	"errors"
	"fmt"

	"github.com/ethereum/go-ethereum/log"

9
	"github.com/ethereum-optimism/optimism/op-node/rollup/event"
10
	"github.com/ethereum-optimism/optimism/op-service/eth"
11
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/superevents"
12 13 14 15
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

type CrossSafeDeps interface {
16
	CrossSafe(chainID eth.ChainID) (pair types.DerivedBlockSealPair, err error)
17 18 19 20

	SafeFrontierCheckDeps
	SafeStartDeps

21 22 23
	CandidateCrossSafe(chain eth.ChainID) (derivedFromScope, crossSafe eth.BlockRef, err error)
	NextDerivedFrom(chain eth.ChainID, derivedFrom eth.BlockID) (after eth.BlockRef, err error)
	PreviousDerived(chain eth.ChainID, derived eth.BlockID) (prevDerived types.BlockSeal, err error)
24

25
	OpenBlock(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error)
26

27
	UpdateCrossSafe(chain eth.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error
28 29
}

30
func CrossSafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossSafeDeps) error {
31 32 33 34 35 36 37 38 39
	logger.Debug("Cross-safe update call")
	// TODO(#11693): establish L1 reorg-lock of scopeDerivedFrom
	// defer unlock once we are done checking the chain
	candidateScope, err := scopedCrossSafeUpdate(logger, chainID, d)
	if err == nil {
		// if we made progress, and no errors, then there is no need to bump the L1 scope yet.
		return nil
	}
	if !errors.Is(err, types.ErrOutOfScope) {
40
		return fmt.Errorf("failed to determine cross-safe update scope of chain %s: %w", chainID, err)
41 42 43 44 45 46 47 48 49 50 51
	}
	// candidateScope is expected to be set if ErrOutOfScope is returned.
	if candidateScope == (eth.BlockRef{}) {
		return fmt.Errorf("expected L1 scope to be defined with ErrOutOfScope: %w", err)
	}
	logger.Debug("Cross-safe updating ran out of L1 scope", "scope", candidateScope, "err", err)
	// bump the L1 scope up, and repeat the prev L2 block, not the candidate
	newScope, err := d.NextDerivedFrom(chainID, candidateScope.ID())
	if err != nil {
		return fmt.Errorf("failed to identify new L1 scope to expand to after %s: %w", candidateScope, err)
	}
52
	currentCrossSafe, err := d.CrossSafe(chainID)
53 54 55 56
	if err != nil {
		// TODO: if genesis isn't cross-safe by default, then we can't register something as cross-safe here
		return fmt.Errorf("failed to identify cross-safe scope to repeat: %w", err)
	}
57
	parent, err := d.PreviousDerived(chainID, currentCrossSafe.Derived.ID())
58 59 60
	if err != nil {
		return fmt.Errorf("cannot find parent-block of cross-safe: %w", err)
	}
61
	crossSafeRef := currentCrossSafe.Derived.MustWithParent(parent.ID())
62 63 64 65 66 67 68 69 70 71 72
	logger.Debug("Bumping cross-safe scope", "scope", newScope, "crossSafe", crossSafeRef)
	if err := d.UpdateCrossSafe(chainID, newScope, crossSafeRef); err != nil {
		return fmt.Errorf("failed to update cross-safe head with L1 scope increment to %s and repeat of L2 block %s: %w", candidateScope, crossSafeRef, err)
	}
	return nil
}

// scopedCrossSafeUpdate runs through the cross-safe update checks.
// If no L2 cross-safe progress can be made without additional L1 input data,
// then a types.ErrOutOfScope error is returned,
// with the current scope that will need to be expanded for further progress.
73
func scopedCrossSafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossSafeDeps) (scope eth.BlockRef, err error) {
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
	candidateScope, candidate, err := d.CandidateCrossSafe(chainID)
	if err != nil {
		return candidateScope, fmt.Errorf("failed to determine candidate block for cross-safe: %w", err)
	}
	logger.Debug("Candidate cross-safe", "scope", candidateScope, "candidate", candidate)
	opened, _, execMsgs, err := d.OpenBlock(chainID, candidate.Number)
	if err != nil {
		return candidateScope, fmt.Errorf("failed to open block %s: %w", candidate, err)
	}
	if opened.ID() != candidate.ID() {
		return candidateScope, fmt.Errorf("unsafe L2 DB has %s, but candidate cross-safe was %s: %w", opened, candidate, types.ErrConflict)
	}
	hazards, err := CrossSafeHazards(d, chainID, candidateScope.ID(), types.BlockSealFromRef(opened), sliceOfExecMsgs(execMsgs))
	if err != nil {
		return candidateScope, fmt.Errorf("failed to determine dependencies of cross-safe candidate %s: %w", candidate, err)
	}
	if err := HazardSafeFrontierChecks(d, candidateScope.ID(), hazards); err != nil {
		return candidateScope, fmt.Errorf("failed to verify block %s in cross-safe frontier: %w", candidate, err)
	}
93
	if err := HazardCycleChecks(d.DependencySet(), d, candidate.Time, hazards); err != nil {
94 95
		return candidateScope, fmt.Errorf("failed to verify block %s in cross-safe check for cycle hazards: %w", candidate, err)
	}
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110

	// promote the candidate block to cross-safe
	if err := d.UpdateCrossSafe(chainID, candidateScope, candidate); err != nil {
		return candidateScope, fmt.Errorf("failed to update cross-safe head to %s, derived from scope %s: %w", candidate, candidateScope, err)
	}
	return candidateScope, nil
}

func sliceOfExecMsgs(execMsgs map[uint32]*types.ExecutingMessage) []*types.ExecutingMessage {
	msgs := make([]*types.ExecutingMessage, 0, len(execMsgs))
	for _, msg := range execMsgs {
		msgs = append(msgs, msg)
	}
	return msgs
}
111 112 113

type CrossSafeWorker struct {
	logger  log.Logger
114
	chainID eth.ChainID
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
	d       CrossSafeDeps
}

func (c *CrossSafeWorker) OnEvent(ev event.Event) bool {
	switch x := ev.(type) {
	case superevents.UpdateCrossSafeRequestEvent:
		if x.ChainID != c.chainID {
			return false
		}
		if err := CrossSafeUpdate(c.logger, c.chainID, c.d); err != nil {
			if errors.Is(err, types.ErrFuture) {
				c.logger.Debug("Worker awaits additional blocks", "err", err)
			} else {
				c.logger.Warn("Failed to process work", "err", err)
			}
		}
	default:
		return false
	}
	return true
}

var _ event.Deriver = (*CrossUnsafeWorker)(nil)

139
func NewCrossSafeWorker(logger log.Logger, chainID eth.ChainID, d CrossSafeDeps) *CrossSafeWorker {
140 141 142 143 144 145 146
	logger = logger.New("chain", chainID)
	return &CrossSafeWorker{
		logger:  logger,
		chainID: chainID,
		d:       d,
	}
}