Skip to content

Commit ec2e3d0

Browse files
committed
Implement UpgradableEngine as middleware for Clique/SystemContract
1 parent ad96fa4 commit ec2e3d0

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed
+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package consensus_wrapper
2+
3+
import (
4+
"github.com/scroll-tech/go-ethereum/common"
5+
"github.com/scroll-tech/go-ethereum/consensus"
6+
"github.com/scroll-tech/go-ethereum/core/state"
7+
"github.com/scroll-tech/go-ethereum/core/types"
8+
"github.com/scroll-tech/go-ethereum/rpc"
9+
"math/big"
10+
"sync"
11+
)
12+
13+
// UpgradableEngine implements consensus.Engine and acts as a middleware to dispatch
14+
// calls to either Clique or SystemContract consensus based on block height.
15+
type UpgradableEngine struct {
16+
// forkBlock is the block number at which the switchover to SystemContract occurs.
17+
forkBlock *big.Int
18+
19+
// clique is the original Clique consensus engine.
20+
clique consensus.Engine
21+
22+
// system is the new SystemContract consensus engine.
23+
system consensus.Engine
24+
}
25+
26+
// NewUpgradableEngine constructs a new upgradable consensus middleware.
27+
func NewUpgradableEngine(forkBlock *big.Int, clique consensus.Engine, system consensus.Engine) *UpgradableEngine {
28+
return &UpgradableEngine{
29+
forkBlock: forkBlock,
30+
clique: clique,
31+
system: system,
32+
}
33+
}
34+
35+
// chooseEngine returns the appropriate consensus engine based on the header's number.
36+
func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine {
37+
if header.Number == nil {
38+
// Fallback: treat as pre-fork if header number is unknown.
39+
return ue.clique
40+
}
41+
// If block number is >= forkBlock, use the new SystemContract consensus, else use Clique.
42+
if header.Number.Cmp(ue.forkBlock) >= 0 {
43+
return ue.system
44+
}
45+
return ue.clique
46+
}
47+
48+
// --------------------
49+
// Methods to implement consensus.Engine
50+
51+
// Author returns the author of the block based on the header.
52+
func (ue *UpgradableEngine) Author(header *types.Header) (common.Address, error) {
53+
return ue.chooseEngine(header).Author(header)
54+
}
55+
56+
// VerifyHeader checks whether a header conforms to the consensus rules of the engine.
57+
func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
58+
return ue.chooseEngine(header).VerifyHeader(chain, header, seal)
59+
}
60+
61+
// VerifyHeaders verifies a batch of headers concurrently. In our use-case,
62+
// headers can only be all system, all clique, or start with clique and then switch once to system.
63+
func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
64+
abort := make(chan struct{})
65+
out := make(chan error)
66+
67+
// If there are no headers, return a closed error channel.
68+
if len(headers) == 0 {
69+
close(out)
70+
return nil, out
71+
}
72+
73+
// Choose engine for the first and last header.
74+
firstEngine := ue.chooseEngine(headers[0])
75+
lastEngine := ue.chooseEngine(headers[len(headers)-1])
76+
77+
// If the first header is system, then all headers must be system.
78+
if firstEngine == ue.system {
79+
return firstEngine.VerifyHeaders(chain, headers, seals)
80+
}
81+
82+
// If first and last headers are both clique, then all headers are clique.
83+
if firstEngine == lastEngine {
84+
return firstEngine.VerifyHeaders(chain, headers, seals)
85+
}
86+
87+
// Otherwise, headers start as clique then switch to system. Since we assume
88+
// a single switchover, find the first header that uses system.
89+
splitIndex := 0
90+
for i, header := range headers {
91+
if ue.chooseEngine(header) == ue.system {
92+
splitIndex = i
93+
break
94+
}
95+
}
96+
// It's expected that splitIndex is > 0.
97+
cliqueHeaders := headers[:splitIndex]
98+
cliqueSeals := seals[:splitIndex]
99+
systemHeaders := headers[splitIndex:]
100+
systemSeals := seals[splitIndex:]
101+
102+
// Create a wait group to merge results.
103+
var wg sync.WaitGroup
104+
wg.Add(2)
105+
106+
// Launch concurrent verifications.
107+
go func() {
108+
defer wg.Done()
109+
_, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals)
110+
for err := range cliqueResults {
111+
select {
112+
case <-abort:
113+
return
114+
case out <- err:
115+
}
116+
}
117+
}()
118+
119+
go func() {
120+
defer wg.Done()
121+
_, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals)
122+
for err := range systemResults {
123+
select {
124+
case <-abort:
125+
return
126+
case out <- err:
127+
}
128+
}
129+
}()
130+
131+
// Close the out channel when both verifications are complete.
132+
go func() {
133+
wg.Wait()
134+
close(out)
135+
}()
136+
137+
return abort, out
138+
}
139+
140+
// Prepare prepares a block header for sealing.
141+
func (ue *UpgradableEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
142+
return ue.chooseEngine(header).Prepare(chain, header)
143+
}
144+
145+
// Seal instructs the engine to start sealing a block.
146+
func (ue *UpgradableEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
147+
return ue.chooseEngine(block.Header()).Seal(chain, block, results, stop)
148+
}
149+
150+
// CalcDifficulty calculates the block difficulty if applicable.
151+
func (ue *UpgradableEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
152+
return ue.chooseEngine(parent).CalcDifficulty(chain, time, parent)
153+
}
154+
155+
// Finalize finalizes the block, applying any post-transaction rules.
156+
func (ue *UpgradableEngine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
157+
ue.chooseEngine(header).Finalize(chain, header, state, txs, uncles)
158+
}
159+
160+
// FinalizeAndAssemble finalizes and assembles a new block.
161+
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) {
162+
return ue.chooseEngine(header).FinalizeAndAssemble(chain, header, state, txs, uncles, receipts)
163+
}
164+
165+
// VerifyUncles verifies that no uncles are attached to the block.
166+
func (ue *UpgradableEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
167+
return ue.chooseEngine(block.Header()).VerifyUncles(chain, block)
168+
}
169+
170+
// APIs returns any RPC APIs exposed by the consensus engine.
171+
func (ue *UpgradableEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API {
172+
// Determine the current chain head.
173+
head := chain.CurrentHeader()
174+
if head == nil {
175+
// Fallback: return the clique APIs (or an empty slice) if we don't have a header.
176+
return ue.clique.APIs(chain)
177+
}
178+
179+
// Choose engine based on whether the chain head is before or after the fork block.
180+
if head.Number.Cmp(ue.forkBlock) >= 0 {
181+
return ue.system.APIs(chain)
182+
}
183+
return ue.clique.APIs(chain)
184+
}
185+
186+
// Close terminates the consensus engine.
187+
func (ue *UpgradableEngine) Close() error {
188+
// Always close both engines.
189+
if err := ue.clique.Close(); err != nil {
190+
return err
191+
}
192+
return ue.system.Close()
193+
}
194+
195+
// SealHash returns the hash of a block prior to it being sealed.
196+
func (ue *UpgradableEngine) SealHash(header *types.Header) common.Hash {
197+
return ue.chooseEngine(header).SealHash(header)
198+
}

eth/ethconfig/config.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package ethconfig
1919

2020
import (
2121
"context"
22+
"github.com/scroll-tech/go-ethereum/consensus/consensus_wrapper"
2223
"math/big"
2324
"os"
2425
"os/user"
@@ -232,13 +233,31 @@ type Config struct {
232233

233234
// CreateConsensusEngine creates a consensus engine for the given chain configuration.
234235
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, l1Client sync_service.EthClient) consensus.Engine {
235-
// If proof-of-authority is requested, set it up
236+
// Case 1: Both SystemContract and Clique are defined: create an upgradable engine.
237+
if chainConfig.SystemContract != nil && chainConfig.Clique != nil {
238+
// Create the Clique engine.
239+
cliqueEngine := clique.New(chainConfig.Clique, db)
240+
// Create the SystemContract engine.
241+
sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client)
242+
243+
// Determine the fork block at which the switch occurs.
244+
var forkBlock *big.Int
245+
if chainConfig.EuclidBlock != nil {
246+
forkBlock = chainConfig.EuclidBlock
247+
}
248+
return consensus_wrapper.NewUpgradableEngine(forkBlock, cliqueEngine, sysEngine)
249+
}
250+
251+
// Case 2: Only the Clique engine is defined.
236252
if chainConfig.Clique != nil {
237253
return clique.New(chainConfig.Clique, db)
238254
}
255+
256+
// Case 3: Only the SystemContract engine is defined.
239257
if chainConfig.SystemContract != nil {
240258
return system_contract.New(context.Background(), chainConfig.SystemContract, l1Client)
241259
}
260+
242261
// Otherwise assume proof-of-work
243262
switch config.PowMode {
244263
case ethash.ModeFake:

params/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ type ChainConfig struct {
633633
CurieBlock *big.Int `json:"curieBlock,omitempty"` // Curie switch block (nil = no fork, 0 = already on curie)
634634
DarwinTime *uint64 `json:"darwinTime,omitempty"` // Darwin switch time (nil = no fork, 0 = already on darwin)
635635
DarwinV2Time *uint64 `json:"darwinv2Time,omitempty"` // DarwinV2 switch time (nil = no fork, 0 = already on darwinv2)
636+
EuclidBlock *big.Int `json:"euclidBlock,omitempty"` // Euclid switch block (nil = no fork, 0 = already on euclid)
636637

637638
// TerminalTotalDifficulty is the amount of total difficulty reached by
638639
// the network that triggers the consensus upgrade.

0 commit comments

Comments
 (0)