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

     1  package driver
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/ethereum/go-ethereum/common"
    10  	"github.com/ethereum/go-ethereum/core/types"
    11  	"github.com/ethereum/go-ethereum/log"
    12  
    13  	"github.com/ethereum-optimism/optimism/op-node/rollup"
    14  	"github.com/ethereum-optimism/optimism/op-node/rollup/async"
    15  	"github.com/ethereum-optimism/optimism/op-node/rollup/conductor"
    16  	"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
    17  	"github.com/ethereum-optimism/optimism/op-service/eth"
    18  )
    19  
    20  type Downloader interface {
    21  	InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error)
    22  	FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error)
    23  }
    24  
    25  type L1OriginSelectorIface interface {
    26  	FindL1Origin(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error)
    27  }
    28  
    29  type SequencerMetrics interface {
    30  	RecordSequencerInconsistentL1Origin(from eth.BlockID, to eth.BlockID)
    31  	RecordSequencerReset()
    32  }
    33  
    34  // Sequencer implements the sequencing interface of the driver: it starts and completes block building jobs.
    35  type Sequencer struct {
    36  	log       log.Logger
    37  	rollupCfg *rollup.Config
    38  
    39  	engine derive.EngineControl
    40  
    41  	attrBuilder      derive.AttributesBuilder
    42  	l1OriginSelector L1OriginSelectorIface
    43  
    44  	metrics SequencerMetrics
    45  
    46  	// timeNow enables sequencer testing to mock the time
    47  	timeNow func() time.Time
    48  
    49  	nextAction time.Time
    50  }
    51  
    52  func NewSequencer(log log.Logger, rollupCfg *rollup.Config, engine derive.EngineControl, attributesBuilder derive.AttributesBuilder, l1OriginSelector L1OriginSelectorIface, metrics SequencerMetrics) *Sequencer {
    53  	return &Sequencer{
    54  		log:              log,
    55  		rollupCfg:        rollupCfg,
    56  		engine:           engine,
    57  		timeNow:          time.Now,
    58  		attrBuilder:      attributesBuilder,
    59  		l1OriginSelector: l1OriginSelector,
    60  		metrics:          metrics,
    61  	}
    62  }
    63  
    64  // StartBuildingBlock initiates a block building job on top of the given L2 head, safe and finalized blocks, and using the provided l1Origin.
    65  func (d *Sequencer) StartBuildingBlock(ctx context.Context) error {
    66  	l2Head := d.engine.UnsafeL2Head()
    67  
    68  	// Figure out which L1 origin block we're going to be building on top of.
    69  	l1Origin, err := d.l1OriginSelector.FindL1Origin(ctx, l2Head)
    70  	if err != nil {
    71  		d.log.Error("Error finding next L1 Origin", "err", err)
    72  		return err
    73  	}
    74  
    75  	if !(l2Head.L1Origin.Hash == l1Origin.ParentHash || l2Head.L1Origin.Hash == l1Origin.Hash) {
    76  		d.metrics.RecordSequencerInconsistentL1Origin(l2Head.L1Origin, l1Origin.ID())
    77  		return derive.NewResetError(fmt.Errorf("cannot build new L2 block with L1 origin %s (parent L1 %s) on current L2 head %s with L1 origin %s", l1Origin, l1Origin.ParentHash, l2Head, l2Head.L1Origin))
    78  	}
    79  
    80  	d.log.Info("creating new block", "parent", l2Head, "l1Origin", l1Origin)
    81  
    82  	fetchCtx, cancel := context.WithTimeout(ctx, time.Second*20)
    83  	defer cancel()
    84  
    85  	attrs, err := d.attrBuilder.PreparePayloadAttributes(fetchCtx, l2Head, l1Origin.ID())
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	// If our next L2 block timestamp is beyond the Sequencer drift threshold, then we must produce
    91  	// empty blocks (other than the L1 info deposit and any user deposits). We handle this by
    92  	// setting NoTxPool to true, which will cause the Sequencer to not include any transactions
    93  	// from the transaction pool.
    94  	attrs.NoTxPool = uint64(attrs.Timestamp) > l1Origin.Time+d.rollupCfg.MaxSequencerDrift
    95  
    96  	// For the Ecotone activation block we shouldn't include any sequencer transactions.
    97  	if d.rollupCfg.IsEcotoneActivationBlock(uint64(attrs.Timestamp)) {
    98  		attrs.NoTxPool = true
    99  		d.log.Info("Sequencing Ecotone upgrade block")
   100  	}
   101  
   102  	d.log.Debug("prepared attributes for new block",
   103  		"num", l2Head.Number+1, "time", uint64(attrs.Timestamp),
   104  		"origin", l1Origin, "origin_time", l1Origin.Time, "noTxPool", attrs.NoTxPool)
   105  
   106  	// Start a payload building process.
   107  	withParent := derive.NewAttributesWithParent(attrs, l2Head, false)
   108  	errTyp, err := d.engine.StartPayload(ctx, l2Head, withParent, false)
   109  	if err != nil {
   110  		return fmt.Errorf("failed to start building on top of L2 chain %s, error (%d): %w", l2Head, errTyp, err)
   111  	}
   112  	return nil
   113  }
   114  
   115  // CompleteBuildingBlock takes the current block that is being built, and asks the engine to complete the building, seal the block, and persist it as canonical.
   116  // Warning: the safe and finalized L2 blocks as viewed during the initiation of the block building are reused for completion of the block building.
   117  // The Execution engine should not change the safe and finalized blocks between start and completion of block building.
   118  func (d *Sequencer) CompleteBuildingBlock(ctx context.Context, agossip async.AsyncGossiper, sequencerConductor conductor.SequencerConductor) (*eth.ExecutionPayloadEnvelope, error) {
   119  	envelope, errTyp, err := d.engine.ConfirmPayload(ctx, agossip, sequencerConductor)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("failed to complete building block: error (%d): %w", errTyp, err)
   122  	}
   123  	return envelope, nil
   124  }
   125  
   126  // CancelBuildingBlock cancels the current open block building job.
   127  // This sequencer only maintains one block building job at a time.
   128  func (d *Sequencer) CancelBuildingBlock(ctx context.Context) {
   129  	// force-cancel, we can always continue block building, and any error is logged by the engine state
   130  	_ = d.engine.CancelPayload(ctx, true)
   131  }
   132  
   133  // PlanNextSequencerAction returns a desired delay till the RunNextSequencerAction call.
   134  func (d *Sequencer) PlanNextSequencerAction() time.Duration {
   135  	// If the engine is busy building safe blocks (and thus changing the head that we would sync on top of),
   136  	// then give it time to sync up.
   137  	if onto, _, safe := d.engine.BuildingPayload(); safe {
   138  		d.log.Warn("delaying sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time)
   139  		// approximates the worst-case time it takes to build a block, to reattempt sequencing after.
   140  		return time.Second * time.Duration(d.rollupCfg.BlockTime)
   141  	}
   142  
   143  	head := d.engine.UnsafeL2Head()
   144  	now := d.timeNow()
   145  
   146  	buildingOnto, buildingID, _ := d.engine.BuildingPayload()
   147  
   148  	// We may have to wait till the next sequencing action, e.g. upon an error.
   149  	// If the head changed we need to respond and will not delay the sequencing.
   150  	if delay := d.nextAction.Sub(now); delay > 0 && buildingOnto.Hash == head.Hash {
   151  		return delay
   152  	}
   153  
   154  	blockTime := time.Duration(d.rollupCfg.BlockTime) * time.Second
   155  	payloadTime := time.Unix(int64(head.Time+d.rollupCfg.BlockTime), 0)
   156  	remainingTime := payloadTime.Sub(now)
   157  
   158  	// If we started building a block already, and if that work is still consistent,
   159  	// then we would like to finish it by sealing the block.
   160  	if buildingID != (eth.PayloadID{}) && buildingOnto.Hash == head.Hash {
   161  		// if we started building already, then we will schedule the sealing.
   162  		if remainingTime < sealingDuration {
   163  			return 0 // if there's not enough time for sealing, don't wait.
   164  		} else {
   165  			// finish with margin of sealing duration before payloadTime
   166  			return remainingTime - sealingDuration
   167  		}
   168  	} else {
   169  		// if we did not yet start building, then we will schedule the start.
   170  		if remainingTime > blockTime {
   171  			// if we have too much time, then wait before starting the build
   172  			return remainingTime - blockTime
   173  		} else {
   174  			// otherwise start instantly
   175  			return 0
   176  		}
   177  	}
   178  }
   179  
   180  // BuildingOnto returns the L2 head reference that the latest block is or was being built on top of.
   181  func (d *Sequencer) BuildingOnto() eth.L2BlockRef {
   182  	ref, _, _ := d.engine.BuildingPayload()
   183  	return ref
   184  }
   185  
   186  // RunNextSequencerAction starts new block building work, or seals existing work,
   187  // and is best timed by first awaiting the delay returned by PlanNextSequencerAction.
   188  // If a new block is successfully sealed, it will be returned for publishing, nil otherwise.
   189  //
   190  // Only critical errors are bubbled up, other errors are handled internally.
   191  // Internally starting or sealing of a block may fail with a derivation-like error:
   192  //   - If it is a critical error, the error is bubbled up to the caller.
   193  //   - If it is a reset error, the ResettableEngineControl used to build blocks is requested to reset, and a backoff applies.
   194  //     No attempt is made at completing the block building.
   195  //   - If it is a temporary error, a backoff is applied to reattempt building later.
   196  //   - If it is any other error, a backoff is applied and building is cancelled.
   197  //
   198  // Upon L1 reorgs that are deep enough to affect the L1 origin selection, a reset-error may occur,
   199  // to direct the engine to follow the new L1 chain before continuing to sequence blocks.
   200  // It is up to the EngineControl implementation to handle conflicting build jobs of the derivation
   201  // process (as verifier) and sequencing process.
   202  // Generally it is expected that the latest call interrupts any ongoing work,
   203  // and the derivation process does not interrupt in the happy case,
   204  // since it can consolidate previously sequenced blocks by comparing sequenced inputs with derived inputs.
   205  // If the derivation pipeline does force a conflicting block, then an ongoing sequencer task might still finish,
   206  // but the derivation can continue to reset until the chain is correct.
   207  // If the engine is currently building safe blocks, then that building is not interrupted, and sequencing is delayed.
   208  func (d *Sequencer) RunNextSequencerAction(ctx context.Context, agossip async.AsyncGossiper, sequencerConductor conductor.SequencerConductor) (*eth.ExecutionPayloadEnvelope, error) {
   209  	// if the engine returns a non-empty payload, OR if the async gossiper already has a payload, we can CompleteBuildingBlock
   210  	if onto, buildingID, safe := d.engine.BuildingPayload(); buildingID != (eth.PayloadID{}) || agossip.Get() != nil {
   211  		if safe {
   212  			d.log.Warn("avoiding sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time)
   213  			// approximates the worst-case time it takes to build a block, to reattempt sequencing after.
   214  			d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.rollupCfg.BlockTime))
   215  			return nil, nil
   216  		}
   217  		envelope, err := d.CompleteBuildingBlock(ctx, agossip, sequencerConductor)
   218  		if err != nil {
   219  			if errors.Is(err, derive.ErrCritical) {
   220  				return nil, err // bubble up critical errors.
   221  			} else if errors.Is(err, derive.ErrReset) {
   222  				d.log.Error("sequencer failed to seal new block, requiring derivation reset", "err", err)
   223  				d.metrics.RecordSequencerReset()
   224  				d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.rollupCfg.BlockTime)) // hold off from sequencing for a full block
   225  				d.CancelBuildingBlock(ctx)
   226  				return nil, err
   227  			} else if errors.Is(err, derive.ErrTemporary) {
   228  				d.log.Error("sequencer failed temporarily to seal new block", "err", err)
   229  				d.nextAction = d.timeNow().Add(time.Second)
   230  				// We don't explicitly cancel block building jobs upon temporary errors: we may still finish the block.
   231  				// Any unfinished block building work eventually times out, and will be cleaned up that way.
   232  			} else {
   233  				d.log.Error("sequencer failed to seal block with unclassified error", "err", err)
   234  				d.nextAction = d.timeNow().Add(time.Second)
   235  				d.CancelBuildingBlock(ctx)
   236  			}
   237  			return nil, nil
   238  		} else {
   239  			payload := envelope.ExecutionPayload
   240  			d.log.Info("sequencer successfully built a new block", "block", payload.ID(), "time", uint64(payload.Timestamp), "txs", len(payload.Transactions))
   241  			return envelope, nil
   242  		}
   243  	} else {
   244  		err := d.StartBuildingBlock(ctx)
   245  		if err != nil {
   246  			if errors.Is(err, derive.ErrCritical) {
   247  				return nil, err
   248  			} else if errors.Is(err, derive.ErrReset) {
   249  				d.log.Error("sequencer failed to seal new block, requiring derivation reset", "err", err)
   250  				d.metrics.RecordSequencerReset()
   251  				d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.rollupCfg.BlockTime)) // hold off from sequencing for a full block
   252  				return nil, err
   253  			} else if errors.Is(err, derive.ErrTemporary) {
   254  				d.log.Error("sequencer temporarily failed to start building new block", "err", err)
   255  				d.nextAction = d.timeNow().Add(time.Second)
   256  			} else {
   257  				d.log.Error("sequencer failed to start building new block with unclassified error", "err", err)
   258  				d.nextAction = d.timeNow().Add(time.Second)
   259  			}
   260  		} else {
   261  			parent, buildingID, _ := d.engine.BuildingPayload() // we should have a new payload ID now that we're building a block
   262  			d.log.Info("sequencer started building new block", "payload_id", buildingID, "l2_parent_block", parent, "l2_parent_block_time", parent.Time)
   263  		}
   264  		return nil, nil
   265  	}
   266  }