github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/derive/engine_queue.go (about)

     1  package derive
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"time"
     9  
    10  	"github.com/ethereum/go-ethereum"
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/core/types"
    13  	"github.com/ethereum/go-ethereum/log"
    14  
    15  	"github.com/ethereum-optimism/optimism/op-node/rollup"
    16  	"github.com/ethereum-optimism/optimism/op-node/rollup/async"
    17  	"github.com/ethereum-optimism/optimism/op-node/rollup/conductor"
    18  	"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
    19  	"github.com/ethereum-optimism/optimism/op-service/eth"
    20  )
    21  
    22  type AttributesWithParent struct {
    23  	attributes   *eth.PayloadAttributes
    24  	parent       eth.L2BlockRef
    25  	isLastInSpan bool
    26  }
    27  
    28  func NewAttributesWithParent(attributes *eth.PayloadAttributes, parent eth.L2BlockRef, isLastInSpan bool) *AttributesWithParent {
    29  	return &AttributesWithParent{attributes, parent, isLastInSpan}
    30  }
    31  
    32  func (a *AttributesWithParent) Attributes() *eth.PayloadAttributes {
    33  	return a.attributes
    34  }
    35  
    36  type NextAttributesProvider interface {
    37  	Origin() eth.L1BlockRef
    38  	NextAttributes(context.Context, eth.L2BlockRef) (*AttributesWithParent, error)
    39  }
    40  
    41  type L2Source interface {
    42  	PayloadByHash(context.Context, common.Hash) (*eth.ExecutionPayloadEnvelope, error)
    43  	PayloadByNumber(context.Context, uint64) (*eth.ExecutionPayloadEnvelope, error)
    44  	L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error)
    45  	L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error)
    46  	L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error)
    47  	SystemConfigL2Fetcher
    48  }
    49  
    50  type Engine interface {
    51  	ExecEngine
    52  	L2Source
    53  }
    54  
    55  // EngineState provides a read-only interface of the forkchoice state properties of the L2 Engine.
    56  type EngineState interface {
    57  	Finalized() eth.L2BlockRef
    58  	UnsafeL2Head() eth.L2BlockRef
    59  	SafeL2Head() eth.L2BlockRef
    60  }
    61  
    62  // EngineControl enables other components to build blocks with the Engine,
    63  // while keeping the forkchoice state and payload-id management internal to
    64  // avoid state inconsistencies between different users of the EngineControl.
    65  type EngineControl interface {
    66  	EngineState
    67  
    68  	// StartPayload requests the engine to start building a block with the given attributes.
    69  	// If updateSafe, the resulting block will be marked as a safe block.
    70  	StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *AttributesWithParent, updateSafe bool) (errType BlockInsertionErrType, err error)
    71  	// ConfirmPayload requests the engine to complete the current block. If no block is being built, or if it fails, an error is returned.
    72  	ConfirmPayload(ctx context.Context, agossip async.AsyncGossiper, sequencerConductor conductor.SequencerConductor) (out *eth.ExecutionPayloadEnvelope, errTyp BlockInsertionErrType, err error)
    73  	// CancelPayload requests the engine to stop building the current block without making it canonical.
    74  	// This is optional, as the engine expires building jobs that are left uncompleted, but can still save resources.
    75  	CancelPayload(ctx context.Context, force bool) error
    76  	// BuildingPayload indicates if a payload is being built, and onto which block it is being built, and whether or not it is a safe payload.
    77  	BuildingPayload() (onto eth.L2BlockRef, id eth.PayloadID, safe bool)
    78  }
    79  
    80  type LocalEngineControl interface {
    81  	EngineControl
    82  	ResetBuildingState()
    83  	IsEngineSyncing() bool
    84  	TryUpdateEngine(ctx context.Context) error
    85  	TryBackupUnsafeReorg(ctx context.Context) (bool, error)
    86  	InsertUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope, ref eth.L2BlockRef) error
    87  
    88  	PendingSafeL2Head() eth.L2BlockRef
    89  	BackupUnsafeL2Head() eth.L2BlockRef
    90  
    91  	SetUnsafeHead(eth.L2BlockRef)
    92  	SetSafeHead(eth.L2BlockRef)
    93  	SetFinalizedHead(eth.L2BlockRef)
    94  	SetPendingSafeL2Head(eth.L2BlockRef)
    95  	SetBackupUnsafeL2Head(block eth.L2BlockRef, triggerReorg bool)
    96  }
    97  
    98  // SafeHeadListener is called when the safe head is updated.
    99  // The safe head may advance by more than one block in a single update
   100  // The l1Block specified is the first L1 block that includes sufficient information to derive the new safe head
   101  type SafeHeadListener interface {
   102  
   103  	// Enabled reports if this safe head listener is actively using the posted data. This allows the engine queue to
   104  	// optionally skip making calls that may be expensive to prepare.
   105  	// Callbacks may still be made if Enabled returns false but are not guaranteed.
   106  	Enabled() bool
   107  
   108  	// SafeHeadUpdated indicates that the safe head has been updated in response to processing batch data
   109  	// The l1Block specified is the first L1 block containing all required batch data to derive newSafeHead
   110  	SafeHeadUpdated(newSafeHead eth.L2BlockRef, l1Block eth.BlockID) error
   111  
   112  	// SafeHeadReset indicates that the derivation pipeline reset back to the specified safe head
   113  	// The L1 block that made the new safe head safe is unknown.
   114  	SafeHeadReset(resetSafeHead eth.L2BlockRef) error
   115  }
   116  
   117  // Max memory used for buffering unsafe payloads
   118  const maxUnsafePayloadsMemory = 500 * 1024 * 1024
   119  
   120  // finalityLookback defines the amount of L1<>L2 relations to track for finalization purposes, one per L1 block.
   121  //
   122  // When L1 finalizes blocks, it finalizes finalityLookback blocks behind the L1 head.
   123  // Non-finality may take longer, but when it does finalize again, it is within this range of the L1 head.
   124  // Thus we only need to retain the L1<>L2 derivation relation data of this many L1 blocks.
   125  //
   126  // In the event of older finalization signals, misconfiguration, or insufficient L1<>L2 derivation relation data,
   127  // then we may miss the opportunity to finalize more L2 blocks.
   128  // This does not cause any divergence, it just causes lagging finalization status.
   129  //
   130  // The beacon chain on mainnet has 32 slots per epoch,
   131  // and new finalization events happen at most 4 epochs behind the head.
   132  // And then we add 1 to make pruning easier by leaving room for a new item without pruning the 32*4.
   133  const finalityLookback = 4*32 + 1
   134  
   135  // finalityDelay is the number of L1 blocks to traverse before trying to finalize L2 blocks again.
   136  // We do not want to do this too often, since it requires fetching a L1 block by number, so no cache data.
   137  const finalityDelay = 64
   138  
   139  // calcFinalityLookback calculates the default finality lookback based on DA challenge window if plasma
   140  // mode is activated or L1 finality lookback.
   141  func calcFinalityLookback(cfg *rollup.Config) uint64 {
   142  	// in plasma mode the longest finality lookback is a commitment is challenged on the last block of
   143  	// the challenge window in which case it will be both challenge + resolve window.
   144  	if cfg.UsePlasma {
   145  		lkb := cfg.DAChallengeWindow + cfg.DAResolveWindow + 1
   146  		// in the case only if the plasma windows are longer than the default finality lookback
   147  		if lkb > finalityLookback {
   148  			return lkb
   149  		}
   150  	}
   151  	return finalityLookback
   152  }
   153  
   154  type FinalityData struct {
   155  	// The last L2 block that was fully derived and inserted into the L2 engine while processing this L1 block.
   156  	L2Block eth.L2BlockRef
   157  	// The L1 block this stage was at when inserting the L2 block.
   158  	// When this L1 block is finalized, the L2 chain up to this block can be fully reproduced from finalized L1 data.
   159  	L1Block eth.BlockID
   160  }
   161  
   162  // EngineQueue queues up payload attributes to consolidate or process with the provided Engine
   163  type EngineQueue struct {
   164  	log log.Logger
   165  	cfg *rollup.Config
   166  
   167  	ec LocalEngineControl
   168  
   169  	// finalizedL1 is the currently perceived finalized L1 block.
   170  	// This may be ahead of the current traversed origin when syncing.
   171  	finalizedL1 eth.L1BlockRef
   172  
   173  	// triedFinalizeAt tracks at which origin we last tried to finalize during sync.
   174  	triedFinalizeAt eth.L1BlockRef
   175  
   176  	// The queued-up attributes
   177  	safeAttributes *AttributesWithParent
   178  	unsafePayloads *PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps and duplicates
   179  
   180  	// Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large.
   181  	finalityData []FinalityData
   182  
   183  	engine L2Source
   184  	prev   NextAttributesProvider
   185  
   186  	origin eth.L1BlockRef   // updated on resets, and whenever we read from the previous stage.
   187  	sysCfg eth.SystemConfig // only used for pipeline resets
   188  
   189  	metrics   Metrics
   190  	l1Fetcher L1Fetcher
   191  
   192  	syncCfg *sync.Config
   193  
   194  	safeHeadNotifs       SafeHeadListener // notified when safe head is updated
   195  	lastNotifiedSafeHead eth.L2BlockRef
   196  }
   197  
   198  // NewEngineQueue creates a new EngineQueue, which should be Reset(origin) before use.
   199  func NewEngineQueue(log log.Logger, cfg *rollup.Config, l2Source L2Source, engine LocalEngineControl, metrics Metrics, prev NextAttributesProvider, l1Fetcher L1Fetcher, syncCfg *sync.Config, safeHeadNotifs SafeHeadListener) *EngineQueue {
   200  	return &EngineQueue{
   201  		log:            log,
   202  		cfg:            cfg,
   203  		ec:             engine,
   204  		engine:         l2Source,
   205  		metrics:        metrics,
   206  		finalityData:   make([]FinalityData, 0, calcFinalityLookback(cfg)),
   207  		unsafePayloads: NewPayloadsQueue(log, maxUnsafePayloadsMemory, payloadMemSize),
   208  		prev:           prev,
   209  		l1Fetcher:      l1Fetcher,
   210  		syncCfg:        syncCfg,
   211  		safeHeadNotifs: safeHeadNotifs,
   212  	}
   213  }
   214  
   215  // Origin identifies the L1 chain (incl.) that included and/or produced all the safe L2 blocks.
   216  func (eq *EngineQueue) Origin() eth.L1BlockRef {
   217  	return eq.origin
   218  }
   219  
   220  func (eq *EngineQueue) SystemConfig() eth.SystemConfig {
   221  	return eq.sysCfg
   222  }
   223  
   224  func (eq *EngineQueue) AddUnsafePayload(envelope *eth.ExecutionPayloadEnvelope) {
   225  	if envelope == nil {
   226  		eq.log.Warn("cannot add nil unsafe payload")
   227  		return
   228  	}
   229  
   230  	if err := eq.unsafePayloads.Push(envelope); err != nil {
   231  		eq.log.Warn("Could not add unsafe payload", "id", envelope.ExecutionPayload.ID(), "timestamp", uint64(envelope.ExecutionPayload.Timestamp), "err", err)
   232  		return
   233  	}
   234  	p := eq.unsafePayloads.Peek()
   235  	eq.metrics.RecordUnsafePayloadsBuffer(uint64(eq.unsafePayloads.Len()), eq.unsafePayloads.MemSize(), p.ExecutionPayload.ID())
   236  	eq.log.Trace("Next unsafe payload to process", "next", p.ExecutionPayload.ID(), "timestamp", uint64(p.ExecutionPayload.Timestamp))
   237  }
   238  
   239  func (eq *EngineQueue) Finalize(l1Origin eth.L1BlockRef) {
   240  	prevFinalizedL1 := eq.finalizedL1
   241  	if l1Origin.Number < eq.finalizedL1.Number {
   242  		eq.log.Error("ignoring old L1 finalized block signal! Is the L1 provider corrupted?", "prev_finalized_l1", prevFinalizedL1, "signaled_finalized_l1", l1Origin)
   243  		return
   244  	}
   245  
   246  	// remember the L1 finalization signal
   247  	eq.finalizedL1 = l1Origin
   248  
   249  	// Sanity check: we only try to finalize L2 immediately, without fetching additional data,
   250  	// if we are on the same chain as the signal.
   251  	// If we are on a different chain, the signal will be ignored,
   252  	// and tryFinalizeL1Origin() will eventually detect that we are on the wrong chain,
   253  	// if not resetting due to reorg elsewhere already.
   254  	for _, fd := range eq.finalityData {
   255  		if fd.L1Block == l1Origin.ID() {
   256  			eq.tryFinalizeL2()
   257  			return
   258  		}
   259  	}
   260  
   261  	eq.log.Info("received L1 finality signal, but missing data for immediate L2 finalization", "prev_finalized_l1", prevFinalizedL1, "signaled_finalized_l1", l1Origin)
   262  }
   263  
   264  // FinalizedL1 identifies the L1 chain (incl.) that included and/or produced all the finalized L2 blocks.
   265  // This may return a zeroed ID if no finalization signals have been seen yet.
   266  func (eq *EngineQueue) FinalizedL1() eth.L1BlockRef {
   267  	return eq.finalizedL1
   268  }
   269  
   270  // LowestQueuedUnsafeBlock returns the block
   271  func (eq *EngineQueue) LowestQueuedUnsafeBlock() eth.L2BlockRef {
   272  	payload := eq.unsafePayloads.Peek()
   273  	if payload == nil {
   274  		return eth.L2BlockRef{}
   275  	}
   276  	ref, err := PayloadToBlockRef(eq.cfg, payload.ExecutionPayload)
   277  	if err != nil {
   278  		return eth.L2BlockRef{}
   279  	}
   280  	return ref
   281  }
   282  
   283  func (eq *EngineQueue) BackupUnsafeL2Head() eth.L2BlockRef {
   284  	return eq.ec.BackupUnsafeL2Head()
   285  }
   286  
   287  // Determine if the engine is syncing to the target block
   288  func (eq *EngineQueue) isEngineSyncing() bool {
   289  	return eq.ec.IsEngineSyncing()
   290  }
   291  
   292  func (eq *EngineQueue) Step(ctx context.Context) error {
   293  	// If we don't need to call FCU to restore unsafeHead using backupUnsafe, keep going b/c
   294  	// this was a no-op(except correcting invalid state when backupUnsafe is empty but TryBackupUnsafeReorg called).
   295  	if fcuCalled, err := eq.ec.TryBackupUnsafeReorg(ctx); fcuCalled {
   296  		// If we needed to perform a network call, then we should yield even if we did not encounter an error.
   297  		return err
   298  	}
   299  	// If we don't need to call FCU, keep going b/c this was a no-op. If we needed to
   300  	// perform a network call, then we should yield even if we did not encounter an error.
   301  	if err := eq.ec.TryUpdateEngine(ctx); !errors.Is(err, errNoFCUNeeded) {
   302  		return err
   303  	}
   304  	// Trying unsafe payload should be done before safe attributes
   305  	// It allows the unsafe head can move forward while the long-range consolidation is in progress.
   306  	if eq.unsafePayloads.Len() > 0 {
   307  		if err := eq.tryNextUnsafePayload(ctx); err != io.EOF {
   308  			return err
   309  		}
   310  		// EOF error means we can't process the next unsafe payload. Then we should process next safe attributes.
   311  	}
   312  	if eq.isEngineSyncing() {
   313  		// The pipeline cannot move forwards if doing EL sync.
   314  		return EngineELSyncing
   315  	}
   316  	if eq.safeAttributes != nil {
   317  		return eq.tryNextSafeAttributes(ctx)
   318  	}
   319  	outOfData := false
   320  	newOrigin := eq.prev.Origin()
   321  	// Check if the L2 unsafe head origin is consistent with the new origin
   322  	if err := eq.verifyNewL1Origin(ctx, newOrigin); err != nil {
   323  		return err
   324  	}
   325  	eq.origin = newOrigin
   326  	// make sure we track the last L2 safe head for every new L1 block
   327  	if err := eq.postProcessSafeL2(); err != nil {
   328  		return err
   329  	}
   330  	// try to finalize the L2 blocks we have synced so far (no-op if L1 finality is behind)
   331  	if err := eq.tryFinalizePastL2Blocks(ctx); err != nil {
   332  		return err
   333  	}
   334  	if next, err := eq.prev.NextAttributes(ctx, eq.ec.PendingSafeL2Head()); err == io.EOF {
   335  		outOfData = true
   336  	} else if err != nil {
   337  		return err
   338  	} else {
   339  		eq.safeAttributes = next
   340  		eq.log.Debug("Adding next safe attributes", "safe_head", eq.ec.SafeL2Head(),
   341  			"pending_safe_head", eq.ec.PendingSafeL2Head(), "next", next)
   342  		return NotEnoughData
   343  	}
   344  
   345  	if outOfData {
   346  		return io.EOF
   347  	} else {
   348  		return nil
   349  	}
   350  }
   351  
   352  // verifyNewL1Origin checks that the L2 unsafe head still has a L1 origin that is on the canonical chain.
   353  // If the unsafe head origin is after the new L1 origin it is assumed to still be canonical.
   354  // The check is only required when moving to a new L1 origin.
   355  func (eq *EngineQueue) verifyNewL1Origin(ctx context.Context, newOrigin eth.L1BlockRef) error {
   356  	if newOrigin == eq.origin {
   357  		return nil
   358  	}
   359  	unsafeOrigin := eq.ec.UnsafeL2Head().L1Origin
   360  	if newOrigin.Number == unsafeOrigin.Number && newOrigin.ID() != unsafeOrigin {
   361  		return NewResetError(fmt.Errorf("l1 origin was inconsistent with l2 unsafe head origin, need reset to resolve: l1 origin: %v; unsafe origin: %v",
   362  			newOrigin.ID(), unsafeOrigin))
   363  	}
   364  	// Avoid requesting an older block by checking against the parent hash
   365  	if newOrigin.Number == unsafeOrigin.Number+1 && newOrigin.ParentHash != unsafeOrigin.Hash {
   366  		return NewResetError(fmt.Errorf("l2 unsafe head origin is no longer canonical, need reset to resolve: canonical hash: %v; unsafe origin hash: %v",
   367  			newOrigin.ParentHash, unsafeOrigin.Hash))
   368  	}
   369  	if newOrigin.Number > unsafeOrigin.Number+1 {
   370  		// If unsafe origin is further behind new origin, check it's still on the canonical chain.
   371  		canonical, err := eq.l1Fetcher.L1BlockRefByNumber(ctx, unsafeOrigin.Number)
   372  		if err != nil {
   373  			return NewTemporaryError(fmt.Errorf("failed to fetch canonical L1 block at slot: %v; err: %w", unsafeOrigin.Number, err))
   374  		}
   375  		if canonical.ID() != unsafeOrigin {
   376  			eq.log.Error("Resetting due to origin mismatch")
   377  			return NewResetError(fmt.Errorf("l2 unsafe head origin is no longer canonical, need reset to resolve: canonical: %v; unsafe origin: %v",
   378  				canonical, unsafeOrigin))
   379  		}
   380  	}
   381  	return nil
   382  }
   383  
   384  func (eq *EngineQueue) tryFinalizePastL2Blocks(ctx context.Context) error {
   385  	if eq.finalizedL1 == (eth.L1BlockRef{}) {
   386  		return nil
   387  	}
   388  
   389  	// If the L1 is finalized beyond the point we are traversing (e.g. during sync),
   390  	// then we should check if we can finalize this L1 block we are traversing.
   391  	// Otherwise, nothing to act on here, we will finalize later on a new finality signal matching the recent history.
   392  	if eq.finalizedL1.Number < eq.origin.Number {
   393  		return nil
   394  	}
   395  
   396  	// If we recently tried finalizing, then don't try again just yet, but traverse more of L1 first.
   397  	if eq.triedFinalizeAt != (eth.L1BlockRef{}) && eq.origin.Number <= eq.triedFinalizeAt.Number+finalityDelay {
   398  		return nil
   399  	}
   400  
   401  	eq.log.Info("processing L1 finality information", "l1_finalized", eq.finalizedL1, "l1_origin", eq.origin, "previous", eq.triedFinalizeAt)
   402  
   403  	// Sanity check we are indeed on the finalizing chain, and not stuck on something else.
   404  	// We assume that the block-by-number query is consistent with the previously received finalized chain signal
   405  	ref, err := eq.l1Fetcher.L1BlockRefByNumber(ctx, eq.origin.Number)
   406  	if err != nil {
   407  		return NewTemporaryError(fmt.Errorf("failed to check if on finalizing L1 chain: %w", err))
   408  	}
   409  	if ref.Hash != eq.origin.Hash {
   410  		return NewResetError(fmt.Errorf("need to reset, we are on %s, not on the finalizing L1 chain %s (towards %s)", eq.origin, ref, eq.finalizedL1))
   411  	}
   412  	eq.tryFinalizeL2()
   413  	return nil
   414  }
   415  
   416  // tryFinalizeL2 traverses the past L1 blocks, checks if any has been finalized,
   417  // and then marks the latest fully derived L2 block from this as finalized,
   418  // or defaults to the current finalized L2 block.
   419  func (eq *EngineQueue) tryFinalizeL2() {
   420  	if eq.finalizedL1 == (eth.L1BlockRef{}) {
   421  		return // if no L1 information is finalized yet, then skip this
   422  	}
   423  	eq.triedFinalizeAt = eq.origin
   424  	// default to keep the same finalized block
   425  	finalizedL2 := eq.ec.Finalized()
   426  	// go through the latest inclusion data, and find the last L2 block that was derived from a finalized L1 block
   427  	for _, fd := range eq.finalityData {
   428  		if fd.L2Block.Number > finalizedL2.Number && fd.L1Block.Number <= eq.finalizedL1.Number {
   429  			finalizedL2 = fd.L2Block
   430  		}
   431  	}
   432  	eq.ec.SetFinalizedHead(finalizedL2)
   433  }
   434  
   435  // postProcessSafeL2 buffers the L1 block the safe head was fully derived from,
   436  // to finalize it once the L1 block, or later, finalizes.
   437  func (eq *EngineQueue) postProcessSafeL2() error {
   438  	if err := eq.notifyNewSafeHead(eq.ec.SafeL2Head()); err != nil {
   439  		return err
   440  	}
   441  	// prune finality data if necessary
   442  	if uint64(len(eq.finalityData)) >= calcFinalityLookback(eq.cfg) {
   443  		eq.finalityData = append(eq.finalityData[:0], eq.finalityData[1:calcFinalityLookback(eq.cfg)]...)
   444  	}
   445  	// remember the last L2 block that we fully derived from the given finality data
   446  	if len(eq.finalityData) == 0 || eq.finalityData[len(eq.finalityData)-1].L1Block.Number < eq.origin.Number {
   447  		// append entry for new L1 block
   448  		eq.finalityData = append(eq.finalityData, FinalityData{
   449  			L2Block: eq.ec.SafeL2Head(),
   450  			L1Block: eq.origin.ID(),
   451  		})
   452  		last := &eq.finalityData[len(eq.finalityData)-1]
   453  		eq.log.Debug("extended finality-data", "last_l1", last.L1Block, "last_l2", last.L2Block)
   454  	} else {
   455  		// if it's a new L2 block that was derived from the same latest L1 block, then just update the entry
   456  		last := &eq.finalityData[len(eq.finalityData)-1]
   457  		if last.L2Block != eq.ec.SafeL2Head() { // avoid logging if there are no changes
   458  			last.L2Block = eq.ec.SafeL2Head()
   459  			eq.log.Debug("updated finality-data", "last_l1", last.L1Block, "last_l2", last.L2Block)
   460  		}
   461  	}
   462  	return nil
   463  }
   464  
   465  // notifyNewSafeHead calls the safe head listener with the current safe head and l1 origin information.
   466  func (eq *EngineQueue) notifyNewSafeHead(safeHead eth.L2BlockRef) error {
   467  	if eq.lastNotifiedSafeHead == safeHead {
   468  		// No change, no need to notify
   469  		return nil
   470  	}
   471  	if err := eq.safeHeadNotifs.SafeHeadUpdated(safeHead, eq.origin.ID()); err != nil {
   472  		// At this point our state is in a potentially inconsistent state as we've updated the safe head
   473  		// in the execution client but failed to post process it. Reset the pipeline so the safe head rolls back
   474  		// a little (it always rolls back at least 1 block) and then it will retry storing the entry
   475  		return NewResetError(fmt.Errorf("safe head notifications failed: %w", err))
   476  	}
   477  	eq.lastNotifiedSafeHead = safeHead
   478  	return nil
   479  }
   480  
   481  func (eq *EngineQueue) logSyncProgress(reason string) {
   482  	eq.log.Info("Sync progress",
   483  		"reason", reason,
   484  		"l2_finalized", eq.ec.Finalized(),
   485  		"l2_safe", eq.ec.SafeL2Head(),
   486  		"l2_pending_safe", eq.ec.PendingSafeL2Head(),
   487  		"l2_unsafe", eq.ec.UnsafeL2Head(),
   488  		"l2_backup_unsafe", eq.ec.BackupUnsafeL2Head(),
   489  		"l2_time", eq.ec.UnsafeL2Head().Time,
   490  		"l1_derived", eq.origin,
   491  	)
   492  }
   493  
   494  func (eq *EngineQueue) tryNextUnsafePayload(ctx context.Context) error {
   495  	firstEnvelope := eq.unsafePayloads.Peek()
   496  	first := firstEnvelope.ExecutionPayload
   497  
   498  	if uint64(first.BlockNumber) <= eq.ec.SafeL2Head().Number {
   499  		eq.log.Info("skipping unsafe payload, since it is older than safe head", "safe", eq.ec.SafeL2Head().ID(), "unsafe", first.ID(), "payload", first.ID())
   500  		eq.unsafePayloads.Pop()
   501  		return nil
   502  	}
   503  	if uint64(first.BlockNumber) <= eq.ec.UnsafeL2Head().Number {
   504  		eq.log.Info("skipping unsafe payload, since it is older than unsafe head", "unsafe", eq.ec.UnsafeL2Head().ID(), "unsafe_payload", first.ID())
   505  		eq.unsafePayloads.Pop()
   506  		return nil
   507  	}
   508  
   509  	// Ensure that the unsafe payload builds upon the current unsafe head
   510  	if first.ParentHash != eq.ec.UnsafeL2Head().Hash {
   511  		if uint64(first.BlockNumber) == eq.ec.UnsafeL2Head().Number+1 {
   512  			eq.log.Info("skipping unsafe payload, since it does not build onto the existing unsafe chain", "safe", eq.ec.SafeL2Head().ID(), "unsafe", first.ID(), "payload", first.ID())
   513  			eq.unsafePayloads.Pop()
   514  		}
   515  		return io.EOF // time to go to next stage if we cannot process the first unsafe payload
   516  	}
   517  
   518  	ref, err := PayloadToBlockRef(eq.cfg, first)
   519  	if err != nil {
   520  		eq.log.Error("failed to decode L2 block ref from payload", "err", err)
   521  		eq.unsafePayloads.Pop()
   522  		return nil
   523  	}
   524  
   525  	if err := eq.ec.InsertUnsafePayload(ctx, firstEnvelope, ref); errors.Is(err, ErrTemporary) {
   526  		eq.log.Debug("Temporary error while inserting unsafe payload", "hash", ref.Hash, "number", ref.Number, "timestamp", ref.Time, "l1Origin", ref.L1Origin)
   527  		return err
   528  	} else if err != nil {
   529  		eq.log.Warn("Dropping invalid unsafe payload", "hash", ref.Hash, "number", ref.Number, "timestamp", ref.Time, "l1Origin", ref.L1Origin)
   530  		eq.unsafePayloads.Pop()
   531  		return err
   532  	}
   533  	eq.unsafePayloads.Pop()
   534  	eq.log.Trace("Executed unsafe payload", "hash", ref.Hash, "number", ref.Number, "timestamp", ref.Time, "l1Origin", ref.L1Origin)
   535  	eq.logSyncProgress("unsafe payload from sequencer")
   536  
   537  	return nil
   538  }
   539  
   540  func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error {
   541  	if eq.safeAttributes == nil { // sanity check the attributes are there
   542  		return nil
   543  	}
   544  	// validate the safe attributes before processing them. The engine may have completed processing them through other means.
   545  	if eq.ec.PendingSafeL2Head() != eq.safeAttributes.parent {
   546  		// Previously the attribute's parent was the pending safe head. If the pending safe head advances so pending safe head's parent is the same as the
   547  		// attribute's parent then we need to cancel the attributes.
   548  		if eq.ec.PendingSafeL2Head().ParentHash == eq.safeAttributes.parent.Hash {
   549  			eq.log.Warn("queued safe attributes are stale, safehead progressed",
   550  				"pending_safe_head", eq.ec.PendingSafeL2Head(), "pending_safe_head_parent", eq.ec.PendingSafeL2Head().ParentID(),
   551  				"attributes_parent", eq.safeAttributes.parent)
   552  			eq.safeAttributes = nil
   553  			return nil
   554  		}
   555  		// If something other than a simple advance occurred, perform a full reset
   556  		return NewResetError(fmt.Errorf("pending safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s",
   557  			eq.ec.PendingSafeL2Head(), eq.ec.PendingSafeL2Head().ParentID(), eq.safeAttributes.parent))
   558  
   559  	}
   560  	if eq.ec.PendingSafeL2Head().Number < eq.ec.UnsafeL2Head().Number {
   561  		return eq.consolidateNextSafeAttributes(ctx)
   562  	} else if eq.ec.PendingSafeL2Head().Number == eq.ec.UnsafeL2Head().Number {
   563  		return eq.forceNextSafeAttributes(ctx)
   564  	} else {
   565  		// For some reason the unsafe head is behind the pending safe head. Log it, and correct it.
   566  		eq.log.Error("invalid sync state, unsafe head is behind pending safe head", "unsafe", eq.ec.UnsafeL2Head(), "pending_safe", eq.ec.PendingSafeL2Head())
   567  		eq.ec.SetUnsafeHead(eq.ec.PendingSafeL2Head())
   568  		return nil
   569  	}
   570  }
   571  
   572  // consolidateNextSafeAttributes tries to match the next safe attributes against the existing unsafe chain,
   573  // to avoid extra processing or unnecessary unwinding of the chain.
   574  // However, if the attributes do not match, they will be forced with forceNextSafeAttributes.
   575  func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error {
   576  	ctx, cancel := context.WithTimeout(ctx, time.Second*10)
   577  	defer cancel()
   578  
   579  	envelope, err := eq.engine.PayloadByNumber(ctx, eq.ec.PendingSafeL2Head().Number+1)
   580  	if err != nil {
   581  		if errors.Is(err, ethereum.NotFound) {
   582  			// engine may have restarted, or inconsistent safe head. We need to reset
   583  			return NewResetError(fmt.Errorf("expected engine was synced and had unsafe block to reconcile, but cannot find the block: %w", err))
   584  		}
   585  		return NewTemporaryError(fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err))
   586  	}
   587  	if err := AttributesMatchBlock(eq.cfg, eq.safeAttributes.attributes, eq.ec.PendingSafeL2Head().Hash, envelope, eq.log); err != nil {
   588  		eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", "err", err, "unsafe", eq.ec.UnsafeL2Head(), "pending_safe", eq.ec.PendingSafeL2Head(), "safe", eq.ec.SafeL2Head())
   589  		// geth cannot wind back a chain without reorging to a new, previously non-canonical, block
   590  		return eq.forceNextSafeAttributes(ctx)
   591  	}
   592  	ref, err := PayloadToBlockRef(eq.cfg, envelope.ExecutionPayload)
   593  	if err != nil {
   594  		return NewResetError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err))
   595  	}
   596  	eq.ec.SetPendingSafeL2Head(ref)
   597  	if eq.safeAttributes.isLastInSpan {
   598  		eq.ec.SetSafeHead(ref)
   599  		if err := eq.postProcessSafeL2(); err != nil {
   600  			return err
   601  		}
   602  	}
   603  	// unsafe head stays the same, we did not reorg the chain.
   604  	eq.safeAttributes = nil
   605  	eq.logSyncProgress("reconciled with L1")
   606  
   607  	return nil
   608  }
   609  
   610  // forceNextSafeAttributes inserts the provided attributes, reorging away any conflicting unsafe chain.
   611  func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error {
   612  	if eq.safeAttributes == nil {
   613  		return nil
   614  	}
   615  	attrs := eq.safeAttributes.attributes
   616  	lastInSpan := eq.safeAttributes.isLastInSpan
   617  	errType, err := eq.StartPayload(ctx, eq.ec.PendingSafeL2Head(), eq.safeAttributes, true)
   618  	if err == nil {
   619  		_, errType, err = eq.ec.ConfirmPayload(ctx, async.NoOpGossiper{}, &conductor.NoOpConductor{})
   620  	}
   621  	if err != nil {
   622  		switch errType {
   623  		case BlockInsertTemporaryErr:
   624  			// RPC errors are recoverable, we can retry the buffered payload attributes later.
   625  			return NewTemporaryError(fmt.Errorf("temporarily cannot insert new safe block: %w", err))
   626  		case BlockInsertPrestateErr:
   627  			_ = eq.CancelPayload(ctx, true)
   628  			return NewResetError(fmt.Errorf("need reset to resolve pre-state problem: %w", err))
   629  		case BlockInsertPayloadErr:
   630  			_ = eq.CancelPayload(ctx, true)
   631  			eq.log.Warn("could not process payload derived from L1 data, dropping batch", "err", err)
   632  			// Count the number of deposits to see if the tx list is deposit only.
   633  			depositCount := 0
   634  			for _, tx := range attrs.Transactions {
   635  				if len(tx) > 0 && tx[0] == types.DepositTxType {
   636  					depositCount += 1
   637  				}
   638  			}
   639  			// Deposit transaction execution errors are suppressed in the execution engine, but if the
   640  			// block is somehow invalid, there is nothing we can do to recover & we should exit.
   641  			// TODO: Can this be triggered by an empty batch with invalid data (like parent hash or gas limit?)
   642  			if len(attrs.Transactions) == depositCount {
   643  				eq.log.Error("deposit only block was invalid", "parent", eq.safeAttributes.parent, "err", err)
   644  				return NewCriticalError(fmt.Errorf("failed to process block with only deposit transactions: %w", err))
   645  			}
   646  			// drop the payload without inserting it
   647  			eq.safeAttributes = nil
   648  			// Revert the pending safe head to the safe head.
   649  			eq.ec.SetPendingSafeL2Head(eq.ec.SafeL2Head())
   650  			// suppress the error b/c we want to retry with the next batch from the batch queue
   651  			// If there is no valid batch the node will eventually force a deposit only block. If
   652  			// the deposit only block fails, this will return the critical error above.
   653  
   654  			// Try to restore to previous known unsafe chain.
   655  			eq.ec.SetBackupUnsafeL2Head(eq.ec.BackupUnsafeL2Head(), true)
   656  
   657  			return nil
   658  		default:
   659  			return NewCriticalError(fmt.Errorf("unknown InsertHeadBlock error type %d: %w", errType, err))
   660  		}
   661  	}
   662  	eq.safeAttributes = nil
   663  	eq.logSyncProgress("processed safe block derived from L1")
   664  	if lastInSpan {
   665  		if err := eq.postProcessSafeL2(); err != nil {
   666  			return err
   667  		}
   668  	}
   669  
   670  	return nil
   671  }
   672  
   673  func (eq *EngineQueue) StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *AttributesWithParent, updateSafe bool) (errType BlockInsertionErrType, err error) {
   674  	return eq.ec.StartPayload(ctx, parent, attrs, updateSafe)
   675  }
   676  
   677  func (eq *EngineQueue) ConfirmPayload(ctx context.Context, agossip async.AsyncGossiper, sequencerConductor conductor.SequencerConductor) (out *eth.ExecutionPayloadEnvelope, errTyp BlockInsertionErrType, err error) {
   678  	return eq.ec.ConfirmPayload(ctx, agossip, sequencerConductor)
   679  }
   680  
   681  func (eq *EngineQueue) CancelPayload(ctx context.Context, force bool) error {
   682  	return eq.ec.CancelPayload(ctx, force)
   683  }
   684  
   685  func (eq *EngineQueue) BuildingPayload() (onto eth.L2BlockRef, id eth.PayloadID, safe bool) {
   686  	return eq.ec.BuildingPayload()
   687  }
   688  
   689  // Reset walks the L2 chain backwards until it finds an L2 block whose L1 origin is canonical.
   690  // The unsafe head is set to the head of the L2 chain, unless the existing safe head is not canonical.
   691  func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error {
   692  	result, err := sync.FindL2Heads(ctx, eq.cfg, eq.l1Fetcher, eq.engine, eq.log, eq.syncCfg)
   693  	if err != nil {
   694  		return NewTemporaryError(fmt.Errorf("failed to find the L2 Heads to start from: %w", err))
   695  	}
   696  	finalized, safe, unsafe := result.Finalized, result.Safe, result.Unsafe
   697  	l1Origin, err := eq.l1Fetcher.L1BlockRefByHash(ctx, safe.L1Origin.Hash)
   698  	if err != nil {
   699  		return NewTemporaryError(fmt.Errorf("failed to fetch the new L1 progress: origin: %v; err: %w", safe.L1Origin, err))
   700  	}
   701  	if safe.Time < l1Origin.Time {
   702  		return NewResetError(fmt.Errorf("cannot reset block derivation to start at L2 block %s with time %d older than its L1 origin %s with time %d, time invariant is broken",
   703  			safe, safe.Time, l1Origin, l1Origin.Time))
   704  	}
   705  
   706  	// Walk back L2 chain to find the L1 origin that is old enough to start buffering channel data from.
   707  	pipelineL2 := safe
   708  	for {
   709  		afterL2Genesis := pipelineL2.Number > eq.cfg.Genesis.L2.Number
   710  		afterL1Genesis := pipelineL2.L1Origin.Number > eq.cfg.Genesis.L1.Number
   711  		afterChannelTimeout := pipelineL2.L1Origin.Number+eq.cfg.ChannelTimeout > l1Origin.Number
   712  		if afterL2Genesis && afterL1Genesis && afterChannelTimeout {
   713  			parent, err := eq.engine.L2BlockRefByHash(ctx, pipelineL2.ParentHash)
   714  			if err != nil {
   715  				return NewResetError(fmt.Errorf("failed to fetch L2 parent block %s", pipelineL2.ParentID()))
   716  			}
   717  			pipelineL2 = parent
   718  		} else {
   719  			break
   720  		}
   721  	}
   722  	pipelineOrigin, err := eq.l1Fetcher.L1BlockRefByHash(ctx, pipelineL2.L1Origin.Hash)
   723  	if err != nil {
   724  		return NewTemporaryError(fmt.Errorf("failed to fetch the new L1 progress: origin: %s; err: %w", pipelineL2.L1Origin, err))
   725  	}
   726  	l1Cfg, err := eq.engine.SystemConfigByL2Hash(ctx, pipelineL2.Hash)
   727  	if err != nil {
   728  		return NewTemporaryError(fmt.Errorf("failed to fetch L1 config of L2 block %s: %w", pipelineL2.ID(), err))
   729  	}
   730  	eq.log.Debug("Reset engine queue", "safeHead", safe, "unsafe", unsafe, "safe_timestamp", safe.Time, "unsafe_timestamp", unsafe.Time, "l1Origin", l1Origin)
   731  	eq.ec.SetUnsafeHead(unsafe)
   732  	eq.ec.SetSafeHead(safe)
   733  	eq.ec.SetPendingSafeL2Head(safe)
   734  	eq.ec.SetFinalizedHead(finalized)
   735  	eq.ec.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false)
   736  	eq.safeAttributes = nil
   737  	eq.ec.ResetBuildingState()
   738  	eq.finalityData = eq.finalityData[:0]
   739  	// note: finalizedL1 and triedFinalizeAt do not reset, since these do not change between reorgs.
   740  	// note: we do not clear the unsafe payloads queue; if the payloads are not applicable anymore the parent hash checks will clear out the old payloads.
   741  	eq.origin = pipelineOrigin
   742  	eq.sysCfg = l1Cfg
   743  	eq.lastNotifiedSafeHead = safe
   744  	if err := eq.safeHeadNotifs.SafeHeadReset(safe); err != nil {
   745  		return err
   746  	}
   747  	if eq.safeHeadNotifs.Enabled() && safe.Number == eq.cfg.Genesis.L2.Number && safe.Hash == eq.cfg.Genesis.L2.Hash {
   748  		// The rollup genesis block is always safe by definition. So if the pipeline resets this far back we know
   749  		// we will process all safe head updates and can record genesis as always safe from L1 genesis.
   750  		// Note that it is not safe to use cfg.Genesis.L1 here as it is the block immediately before the L2 genesis
   751  		// but the contracts may have been deployed earlier than that, allowing creating a dispute game
   752  		// with a L1 head prior to cfg.Genesis.L1
   753  		l1Genesis, err := eq.l1Fetcher.L1BlockRefByNumber(ctx, 0)
   754  		if err != nil {
   755  			return fmt.Errorf("failed to retrieve L1 genesis: %w", err)
   756  		}
   757  		if err := eq.safeHeadNotifs.SafeHeadUpdated(safe, l1Genesis.ID()); err != nil {
   758  			return err
   759  		}
   760  	}
   761  	eq.logSyncProgress("reset derivation work")
   762  	return io.EOF
   763  }
   764  
   765  // UnsafeL2SyncTarget retrieves the first queued-up L2 unsafe payload, or a zeroed reference if there is none.
   766  func (eq *EngineQueue) UnsafeL2SyncTarget() eth.L2BlockRef {
   767  	if first := eq.unsafePayloads.Peek(); first != nil {
   768  		ref, err := PayloadToBlockRef(eq.cfg, first.ExecutionPayload)
   769  		if err != nil {
   770  			return eth.L2BlockRef{}
   771  		}
   772  		return ref
   773  	} else {
   774  		return eth.L2BlockRef{}
   775  	}
   776  }