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 }