Skip to content

Commit 3c454e7

Browse files
authored
fix: correctly select consensus engine during EuclidV2 transition (#1130)
* fix: correctly select consensus engine during EuclidV2 transition * correctly backdate transition blocks * nit * correctly unset fields * nit
1 parent 4797ee9 commit 3c454e7

File tree

9 files changed

+145
-59
lines changed

9 files changed

+145
-59
lines changed

consensus/clique/clique.go

+21-6
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,29 @@ func (c *Clique) verifySeal(snap *Snapshot, header *types.Header, parents []*typ
507507
return nil
508508
}
509509

510+
func (c *Clique) CalcTimestamp(parent *types.Header) uint64 {
511+
timestamp := parent.Time + c.config.Period
512+
513+
// If RelaxedPeriod is enabled, always set the header timestamp to now (ie the time we start building it) as
514+
// we don't know when it will be sealed
515+
if c.config.RelaxedPeriod || timestamp < uint64(time.Now().Unix()) {
516+
timestamp = uint64(time.Now().Unix())
517+
}
518+
519+
return timestamp
520+
}
521+
510522
// Prepare implements consensus.Engine, preparing all the consensus fields of the
511523
// header for running the transactions on top.
512-
func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
524+
func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header, timeOverride *uint64) error {
513525
// If the block isn't a checkpoint, cast a random vote (good enough for now)
514526
header.Coinbase = common.Address{}
515527
header.Nonce = types.BlockNonce{}
516528

529+
// Unset EuclidV2-related fields
530+
header.BlockSignature = nil
531+
header.IsEuclidV2 = false
532+
517533
number := header.Number.Uint64()
518534
// Assemble the voting snapshot to check which votes make sense
519535
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
@@ -568,11 +584,10 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
568584
if parent == nil {
569585
return consensus.ErrUnknownAncestor
570586
}
571-
header.Time = parent.Time + c.config.Period
572-
// If RelaxedPeriod is enabled, always set the header timestamp to now (ie the time we start building it) as
573-
// we don't know when it will be sealed
574-
if c.config.RelaxedPeriod || header.Time < uint64(time.Now().Unix()) {
575-
header.Time = uint64(time.Now().Unix())
587+
if timeOverride != nil {
588+
header.Time = *timeOverride
589+
} else {
590+
header.Time = c.CalcTimestamp(parent)
576591
}
577592
return nil
578593
}

consensus/consensus.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ type Engine interface {
7979

8080
// Prepare initializes the consensus fields of a block header according to the
8181
// rules of a particular engine. The changes are executed inline.
82-
Prepare(chain ChainHeaderReader, header *types.Header) error
82+
Prepare(chain ChainHeaderReader, header *types.Header, timeOverride *uint64) error
8383

8484
// Finalize runs any post-transaction state modifications (e.g. block rewards)
8585
// but does not assemble the block.
@@ -111,6 +111,10 @@ type Engine interface {
111111
// that a new block should have.
112112
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
113113

114+
// CalcTimestamp predicts the next block's timestamp based on its parent.
115+
// Note, this method is only called from UpgradableEngine.
116+
CalcTimestamp(parent *types.Header) uint64
117+
114118
// APIs returns the RPC APIs this consensus engine provides.
115119
APIs(chain ChainHeaderReader) []rpc.API
116120

consensus/ethash/consensus.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -579,9 +579,13 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *type
579579
return nil
580580
}
581581

582+
func (ue *Ethash) CalcTimestamp(parent *types.Header) uint64 {
583+
panic("Called CalcTimestamp on Ethash, not implemented")
584+
}
585+
582586
// Prepare implements consensus.Engine, initializing the difficulty field of a
583587
// header to conform to the ethash protocol. The changes are done inline.
584-
func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
588+
func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header, timeOverride *uint64) error {
585589
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
586590
if parent == nil {
587591
return consensus.ErrUnknownAncestor

consensus/system_contract/consensus.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,21 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types.
225225
return nil
226226
}
227227

228+
func (s *SystemContract) CalcTimestamp(parent *types.Header) uint64 {
229+
timestamp := parent.Time + s.config.Period
230+
231+
// If RelaxedPeriod is enabled, always set the header timestamp to now (ie the time we start building it) as
232+
// we don't know when it will be sealed
233+
if s.config.RelaxedPeriod || timestamp < uint64(time.Now().Unix()) {
234+
timestamp = uint64(time.Now().Unix())
235+
}
236+
237+
return timestamp
238+
}
239+
228240
// Prepare initializes the consensus fields of a block header according to the
229241
// rules of a particular engine. Update only timestamp and prepare ExtraData for Signature
230-
func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
242+
func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header, timeOverride *uint64) error {
231243
// Make sure unused fields are empty
232244
header.Coinbase = common.Address{}
233245
header.Nonce = types.BlockNonce{}
@@ -243,11 +255,10 @@ func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *type
243255
if parent == nil {
244256
return consensus.ErrUnknownAncestor
245257
}
246-
header.Time = parent.Time + s.config.Period
247-
// If RelaxedPeriod is enabled, always set the header timestamp to now (ie the time we start building it) as
248-
// we don't know when it will be sealed
249-
if s.config.RelaxedPeriod || header.Time < uint64(time.Now().Unix()) {
250-
header.Time = uint64(time.Now().Unix())
258+
if timeOverride != nil {
259+
header.Time = *timeOverride
260+
} else {
261+
header.Time = s.CalcTimestamp(parent)
251262
}
252263

253264
// Difficulty must be 1

consensus/wrapper/consensus.go

+58-15
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ func NewUpgradableEngine(isUpgraded func(uint64) bool, clique consensus.Engine,
3939
}
4040

4141
// chooseEngine returns the appropriate consensus engine based on the header's timestamp.
42-
func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine {
43-
if ue.isUpgraded(header.Time) {
42+
func (ue *UpgradableEngine) chooseEngine(timestamp uint64) consensus.Engine {
43+
if ue.isUpgraded(timestamp) {
4444
return ue.system
4545
}
4646
return ue.clique
@@ -51,12 +51,12 @@ func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine
5151

5252
// Author returns the author of the block based on the header.
5353
func (ue *UpgradableEngine) Author(header *types.Header) (common.Address, error) {
54-
return ue.chooseEngine(header).Author(header)
54+
return ue.chooseEngine(header.Time).Author(header)
5555
}
5656

5757
// VerifyHeader checks whether a header conforms to the consensus rules of the engine.
5858
func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
59-
return ue.chooseEngine(header).VerifyHeader(chain, header, seal)
59+
return ue.chooseEngine(header.Time).VerifyHeader(chain, header, seal)
6060
}
6161

6262
// VerifyHeaders verifies a batch of headers concurrently. In our use-case,
@@ -72,8 +72,8 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea
7272
}
7373

7474
// Choose engine for the first and last header.
75-
firstEngine := ue.chooseEngine(headers[0])
76-
lastEngine := ue.chooseEngine(headers[len(headers)-1])
75+
firstEngine := ue.chooseEngine(headers[0].Time)
76+
lastEngine := ue.chooseEngine(headers[len(headers)-1].Time)
7777

7878
// If the first header is system, then all headers must be system.
7979
if firstEngine == ue.system {
@@ -89,7 +89,7 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea
8989
// a single switchover, find the first header that uses system.
9090
splitIndex := 0
9191
for i, header := range headers {
92-
if ue.chooseEngine(header) == ue.system {
92+
if ue.chooseEngine(header.Time) == ue.system {
9393
splitIndex = i
9494
break
9595
}
@@ -151,34 +151,77 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea
151151
return abort, results
152152
}
153153

154+
func (ue *UpgradableEngine) CalcTimestamp(parent *types.Header) uint64 {
155+
panic("Called CalcTimestamp on UpgradableEngine, not implemented")
156+
}
157+
154158
// Prepare prepares a block header for sealing.
155-
func (ue *UpgradableEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
156-
return ue.chooseEngine(header).Prepare(chain, header)
159+
func (ue *UpgradableEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header, timeOverride *uint64) error {
160+
// Override provided => select engine based on override timestamp.
161+
if timeOverride != nil {
162+
return ue.chooseEngine(*timeOverride).Prepare(chain, header, timeOverride)
163+
}
164+
165+
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
166+
if parent == nil {
167+
return consensus.ErrUnknownAncestor
168+
}
169+
170+
// Check if parent is pre- or post-EuclidV2.
171+
if ue.chooseEngine(parent.Time) == ue.clique {
172+
// This is either a normal Clique block, or the EuclidV2 transition block.
173+
174+
time := ue.clique.CalcTimestamp(parent)
175+
176+
if ue.chooseEngine(time) == ue.clique {
177+
// We're still in Clique mode.
178+
// Note: We must provide timestamp override to avoid the edge case
179+
// where we slip over into EuclidV2 in this next call.
180+
return ue.clique.Prepare(chain, header, &time)
181+
} else {
182+
// This is the EuclidV2 transition block.
183+
return ue.system.Prepare(chain, header, &time)
184+
}
185+
} else {
186+
// We are already post EuclidV2, in SystemContract mode.
187+
188+
time := ue.system.CalcTimestamp(parent)
189+
190+
if ue.chooseEngine(time) == ue.clique {
191+
// Somehow we slipped back to Clique, override with a known post-EuclidV2 timestamp.
192+
// Note: This should not happen in practice.
193+
log.Error("Parent is post-EuclidV2 but SystemContract set pre-EuclidV2 timestamp, overriding", "blockNumber", header.Number, "parentTime", parent.Time, "systemContractTime", time)
194+
return ue.system.Prepare(chain, header, &parent.Time)
195+
} else {
196+
// We are already in SystemContract mode.
197+
return ue.system.Prepare(chain, header, &time)
198+
}
199+
}
157200
}
158201

159202
// Seal instructs the engine to start sealing a block.
160203
func (ue *UpgradableEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
161-
return ue.chooseEngine(block.Header()).Seal(chain, block, results, stop)
204+
return ue.chooseEngine(block.Time()).Seal(chain, block, results, stop)
162205
}
163206

164207
// CalcDifficulty calculates the block difficulty if applicable.
165208
func (ue *UpgradableEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
166-
return ue.chooseEngine(parent).CalcDifficulty(chain, time, parent)
209+
return ue.chooseEngine(parent.Time).CalcDifficulty(chain, time, parent)
167210
}
168211

169212
// Finalize finalizes the block, applying any post-transaction rules.
170213
func (ue *UpgradableEngine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
171-
ue.chooseEngine(header).Finalize(chain, header, state, txs, uncles)
214+
ue.chooseEngine(header.Time).Finalize(chain, header, state, txs, uncles)
172215
}
173216

174217
// FinalizeAndAssemble finalizes and assembles a new block.
175218
func (ue *UpgradableEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
176-
return ue.chooseEngine(header).FinalizeAndAssemble(chain, header, state, txs, uncles, receipts)
219+
return ue.chooseEngine(header.Time).FinalizeAndAssemble(chain, header, state, txs, uncles, receipts)
177220
}
178221

179222
// VerifyUncles verifies that no uncles are attached to the block.
180223
func (ue *UpgradableEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
181-
return ue.chooseEngine(block.Header()).VerifyUncles(chain, block)
224+
return ue.chooseEngine(block.Time()).VerifyUncles(chain, block)
182225
}
183226

184227
// APIs returns any RPC APIs exposed by the consensus engine.
@@ -203,7 +246,7 @@ func (ue *UpgradableEngine) Close() error {
203246

204247
// SealHash returns the hash of a block prior to it being sealed.
205248
func (ue *UpgradableEngine) SealHash(header *types.Header) common.Hash {
206-
return ue.chooseEngine(header).SealHash(header)
249+
return ue.chooseEngine(header.Time).SealHash(header)
207250
}
208251

209252
// Authorize injects a private key into the consensus engine to mint new blocks

core/blockchain.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -1832,16 +1832,13 @@ func (bc *BlockChain) BuildAndWriteBlock(parentBlock *types.Block, header *types
18321832
// This should be done with https://github.com/scroll-tech/go-ethereum/pull/913.
18331833

18341834
if sign {
1835-
// remember the time as Clique will override it
1835+
// Prevent Engine from overriding timestamp.
18361836
originalTime := header.Time
18371837

1838-
err = bc.engine.Prepare(bc, header)
1838+
err = bc.engine.Prepare(bc, header, &originalTime)
18391839
if err != nil {
18401840
return nil, NonStatTy, fmt.Errorf("error preparing block %d: %w", tempBlock.Number().Uint64(), err)
18411841
}
1842-
1843-
// we want to re-sign the block: set time to original value again.
1844-
header.Time = originalTime
18451842
}
18461843

18471844
// finalize and assemble block as fullBlock: replicates consensus.FinalizeAndAssemble()

eth/catalyst/api.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD
150150
parentL1BaseFee := fees.GetL1BaseFee(stateDb)
151151
header.BaseFee = misc.CalcBaseFee(config, parent.Header(), parentL1BaseFee)
152152
}
153-
err = api.eth.Engine().Prepare(bc, header)
153+
err = api.eth.Engine().Prepare(bc, header, nil)
154154
if err != nil {
155155
return nil, err
156156
}

miner/scroll_worker.go

+35-23
Original file line numberDiff line numberDiff line change
@@ -447,29 +447,11 @@ func (w *worker) updateSnapshot() {
447447
// collectPendingL1Messages reads pending L1 messages from the database.
448448
// It returns a list of L1 messages that can be included in the block. Depending on the current
449449
// block time, it reads L1 messages from either L1MessageQueueV1 or L1MessageQueueV2.
450-
// It also makes sure that all L1 messages V1 are consumed before we activate EuclidV2 fork by backdating the block's time
451-
// to the parent block's timestamp.
452450
func (w *worker) collectPendingL1Messages(startIndex uint64) []types.L1MessageTx {
453451
maxCount := w.chainConfig.Scroll.L1Config.NumL1MessagesPerBlock
454452

455453
// If we are on EuclidV2, we need to read L1 messages from L1MessageQueueV2.
456454
if w.chainConfig.IsEuclidV2(w.current.header.Time) {
457-
parent := w.chain.CurrentHeader()
458-
459-
// w.current would be the first block in the EuclidV2 fork
460-
if !w.chainConfig.IsEuclidV2(parent.Time) {
461-
// We need to make sure that all the L1 messages V1 are consumed before we activate EuclidV2 as with EuclidV2
462-
// only L1 messages V2 are allowed.
463-
l1MessagesV1 := rawdb.ReadL1MessagesV1From(w.eth.ChainDb(), startIndex, maxCount)
464-
if len(l1MessagesV1) > 0 {
465-
// backdate the block to the parent block's timestamp -> not yet EuclidV2
466-
// TODO: might need to re-run Prepare here
467-
log.Warn("Back-labeling header timestamp to ensure it precedes the EuclidV2 transition", "blockNumber", w.current.header.Number, "oldTime", w.current.header.Time, "newTime", parent.Time)
468-
w.current.header.Time = parent.Time
469-
return l1MessagesV1
470-
}
471-
}
472-
473455
return rawdb.ReadL1MessagesV2From(w.eth.ChainDb(), startIndex, maxCount)
474456
}
475457

@@ -512,6 +494,11 @@ func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, r
512494
header.Coinbase = w.coinbase
513495
}
514496

497+
var nextL1MsgIndex uint64
498+
if dbVal := rawdb.ReadFirstQueueIndexNotInL2Block(w.eth.ChainDb(), header.ParentHash); dbVal != nil {
499+
nextL1MsgIndex = *dbVal
500+
}
501+
515502
if w.config.SigningDisabled {
516503
// Need to make sure to set difficulty so that a new canonical chain is detected in Blockchain
517504
header.Difficulty = new(big.Int).SetUint64(1)
@@ -520,15 +507,40 @@ func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, r
520507
header.Nonce = types.BlockNonce{}
521508
} else {
522509
prepareStart := time.Now()
523-
if err := w.engine.Prepare(w.chain, header); err != nil {
510+
// Note: this call will set header.Time, among other fields.
511+
if err := w.engine.Prepare(w.chain, header, nil); err != nil {
524512
return fmt.Errorf("failed to prepare header for mining: %w", err)
525513
}
526514
prepareTimer.UpdateSince(prepareStart)
527-
}
528515

529-
var nextL1MsgIndex uint64
530-
if dbVal := rawdb.ReadFirstQueueIndexNotInL2Block(w.eth.ChainDb(), header.ParentHash); dbVal != nil {
531-
nextL1MsgIndex = *dbVal
516+
if w.chainConfig.IsEuclidV2(header.Time) && !w.chainConfig.IsEuclidV2(parent.Time()) {
517+
// We found a potential EuclidV2 transition block.
518+
// We need to make sure that all the L1 messages V1 are consumed before we activate EuclidV2,
519+
// since we can only include MessageQueueV2 messages after EuclidV2.
520+
l1MessagesV1 := rawdb.ReadL1MessagesV1From(w.eth.ChainDb(), nextL1MsgIndex, 1)
521+
if len(l1MessagesV1) > 0 {
522+
// Reset Extra (it was unset by SystemContract)
523+
header.Extra = w.extra
524+
525+
// Backdate the block to the parent block's timestamp -> not yet EuclidV2
526+
parentTime := parent.Time()
527+
log.Warn("Backdating header timestamp to ensure it precedes the EuclidV2 transition", "blockNumber", header.Number, "oldTime", header.Time, "newTime", parentTime)
528+
529+
// Run Prepare again, this time we provide a timestamp override, so it will use Clique.
530+
// Note: Clique should correctly unset or overwrite any fields previously set by SystemConfig,
531+
// with the exception of Extra that was reset above.
532+
prepareStart := time.Now()
533+
if err := w.engine.Prepare(w.chain, header, &parentTime); err != nil {
534+
return fmt.Errorf("failed to prepare header for mining: %w", err)
535+
}
536+
prepareTimer.UpdateSince(prepareStart)
537+
} else {
538+
// Only print log if we are the sequencer -- otherwise we will print confusing logs for the pending block.
539+
if w.isRunning() {
540+
log.Info("All MessageQueueV1 messages processed, creating EuclidV2 transition block", "blockNumber", header.Number, "blockTime", header.Time, "firstV2MsgIndex", nextL1MsgIndex)
541+
}
542+
}
543+
}
532544
}
533545

534546
vmConfig := *w.chain.GetVMConfig()

params/version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
const (
2525
VersionMajor = 5 // Major version component of the current release
2626
VersionMinor = 8 // Minor version component of the current release
27-
VersionPatch = 19 // Patch version component of the current release
27+
VersionPatch = 20 // Patch version component of the current release
2828
VersionMeta = "mainnet" // Version metadata to append to the version string
2929
)
3030

0 commit comments

Comments
 (0)