Skip to content

Commit 51c7eee

Browse files
authored
feat(clique): allow shadowforking a clique network (#828)
1 parent 9abdd5b commit 51c7eee

File tree

14 files changed

+292
-48
lines changed

14 files changed

+292
-48
lines changed

cmd/geth/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ var (
169169
utils.L1DeploymentBlockFlag,
170170
utils.CircuitCapacityCheckEnabledFlag,
171171
utils.RollupVerifyEnabledFlag,
172+
utils.ShadowforkPeersFlag,
172173
}
173174

174175
rpcFlags = []cli.Flag{

cmd/geth/usage.go

+1
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
237237
utils.BloomFilterSizeFlag,
238238
cli.HelpFlag,
239239
utils.CatalystFlag,
240+
utils.ShadowforkPeersFlag,
240241
},
241242
},
242243
}

cmd/utils/flags.go

+10
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,12 @@ var (
858858
Name: "rpc.getlogs.maxrange",
859859
Usage: "Limit max fetched block range for `eth_getLogs` method",
860860
}
861+
862+
// Shadowfork peers
863+
ShadowforkPeersFlag = cli.StringSliceFlag{
864+
Name: "net.shadowforkpeers",
865+
Usage: "peer ids of shadow fork peers",
866+
}
861867
)
862868

863869
// MakeDataDir retrieves the currently requested data directory, terminating
@@ -1651,6 +1657,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
16511657
setCircuitCapacityCheck(ctx, cfg)
16521658
setEnableRollupVerify(ctx, cfg)
16531659
setMaxBlockRange(ctx, cfg)
1660+
if ctx.GlobalIsSet(ShadowforkPeersFlag.Name) {
1661+
cfg.ShadowForkPeerIDs = ctx.GlobalStringSlice(ShadowforkPeersFlag.Name)
1662+
log.Info("Shadow fork peers", "ids", cfg.ShadowForkPeerIDs)
1663+
}
16541664

16551665
// Cap the cache allowance and tune the garbage collector
16561666
mem, err := gopsutil.VirtualMemory()

consensus/clique/clique.go

+22-11
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ var (
6666

6767
uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
6868

69-
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
70-
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures
69+
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
70+
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures
71+
diffShadowFork = diffNoTurn
7172
)
7273

7374
// Various error messages to mark blocks invalid. These should be private to
@@ -195,6 +196,7 @@ func New(config *params.CliqueConfig, db ethdb.Database) *Clique {
195196
if conf.Epoch == 0 {
196197
conf.Epoch = epochLength
197198
}
199+
198200
// Allocate the snapshot caches and create the engine
199201
recents, _ := lru.NewARC(inmemorySnapshots)
200202
signatures, _ := lru.NewARC(inmemorySignatures)
@@ -291,7 +293,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
291293
}
292294
// Ensure that the block's difficulty is meaningful (may not be correct at this point)
293295
if number > 0 {
294-
if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0) {
296+
if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0 && header.Difficulty.Cmp(diffShadowFork) != 0) {
295297
return errInvalidDifficulty
296298
}
297299
}
@@ -375,6 +377,14 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash
375377
snap *Snapshot
376378
)
377379
for snap == nil {
380+
if c.config.ShadowForkHeight > 0 && number == c.config.ShadowForkHeight {
381+
c.signatures.Purge()
382+
c.recents.Purge()
383+
c.proposals = make(map[common.Address]bool)
384+
snap = newSnapshot(c.config, c.signatures, number, hash, []common.Address{c.config.ShadowForkSigner})
385+
break
386+
}
387+
378388
// If an in-memory snapshot was found, use that
379389
if s, ok := c.recents.Get(hash); ok {
380390
snap = s.(*Snapshot)
@@ -485,11 +495,8 @@ func (c *Clique) verifySeal(snap *Snapshot, header *types.Header, parents []*typ
485495
}
486496
// Ensure that the difficulty corresponds to the turn-ness of the signer
487497
if !c.fakeDiff {
488-
inturn := snap.inturn(header.Number.Uint64(), signer)
489-
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
490-
return errWrongDifficulty
491-
}
492-
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
498+
expected := c.calcDifficulty(snap, signer)
499+
if header.Difficulty.Cmp(expected) != 0 {
493500
return errWrongDifficulty
494501
}
495502
}
@@ -534,7 +541,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
534541
c.lock.RUnlock()
535542

536543
// Set the correct difficulty
537-
header.Difficulty = calcDifficulty(snap, signer)
544+
header.Difficulty = c.calcDifficulty(snap, signer)
538545

539546
// Ensure the extra data has all its components
540547
if len(header.Extra) < extraVanity {
@@ -678,10 +685,14 @@ func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64,
678685
c.lock.RLock()
679686
signer := c.signer
680687
c.lock.RUnlock()
681-
return calcDifficulty(snap, signer)
688+
return c.calcDifficulty(snap, signer)
682689
}
683690

684-
func calcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
691+
func (c *Clique) calcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
692+
if c.config.ShadowForkHeight > 0 && snap.Number >= c.config.ShadowForkHeight {
693+
// if we are past shadow fork point, set a low difficulty so that mainnet nodes don't try to switch to forked chain
694+
return new(big.Int).Set(diffShadowFork)
695+
}
685696
if snap.inturn(snap.Number+1, signer) {
686697
return new(big.Int).Set(diffInTurn)
687698
}

consensus/clique/clique_test.go

+89
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package clique
1818

1919
import (
20+
"bytes"
2021
"math/big"
22+
"strings"
2123
"testing"
2224

2325
"github.com/scroll-tech/go-ethereum/common"
@@ -125,3 +127,90 @@ func TestSealHash(t *testing.T) {
125127
t.Errorf("have %x, want %x", have, want)
126128
}
127129
}
130+
131+
func TestShadowFork(t *testing.T) {
132+
engineConf := *params.AllCliqueProtocolChanges.Clique
133+
engineConf.Epoch = 2
134+
forkedEngineConf := engineConf
135+
forkedEngineConf.ShadowForkHeight = 3
136+
shadowForkKey, _ := crypto.HexToECDSA(strings.Repeat("11", 32))
137+
shadowForkAddr := crypto.PubkeyToAddress(shadowForkKey.PublicKey)
138+
forkedEngineConf.ShadowForkSigner = shadowForkAddr
139+
140+
// Initialize a Clique chain with a single signer
141+
var (
142+
db = rawdb.NewMemoryDatabase()
143+
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
144+
addr = crypto.PubkeyToAddress(key.PublicKey)
145+
engine = New(&engineConf, db)
146+
signer = new(types.HomesteadSigner)
147+
forkedEngine = New(&forkedEngineConf, db)
148+
)
149+
genspec := &core.Genesis{
150+
ExtraData: make([]byte, extraVanity+common.AddressLength+extraSeal),
151+
Alloc: map[common.Address]core.GenesisAccount{
152+
addr: {Balance: big.NewInt(10000000000000000)},
153+
},
154+
BaseFee: big.NewInt(params.InitialBaseFee),
155+
}
156+
copy(genspec.ExtraData[extraVanity:], addr[:])
157+
genesis := genspec.MustCommit(db)
158+
159+
// Generate a batch of blocks, each properly signed
160+
chain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil)
161+
defer chain.Stop()
162+
163+
forkedChain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, forkedEngine, vm.Config{}, nil, nil)
164+
defer forkedChain.Stop()
165+
166+
blocks, _ := core.GenerateChain(params.AllCliqueProtocolChanges, genesis, forkedEngine, db, 16, func(i int, block *core.BlockGen) {
167+
// The chain maker doesn't have access to a chain, so the difficulty will be
168+
// lets unset (nil). Set it here to the correct value.
169+
if block.Number().Uint64() > forkedEngineConf.ShadowForkHeight {
170+
block.SetDifficulty(diffShadowFork)
171+
} else {
172+
block.SetDifficulty(diffInTurn)
173+
}
174+
175+
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr), common.Address{0x00}, new(big.Int), params.TxGas, block.BaseFee(), nil), signer, key)
176+
if err != nil {
177+
panic(err)
178+
}
179+
block.AddTxWithChain(chain, tx)
180+
})
181+
for i, block := range blocks {
182+
header := block.Header()
183+
if i > 0 {
184+
header.ParentHash = blocks[i-1].Hash()
185+
}
186+
187+
signingAddr, signingKey := addr, key
188+
if header.Number.Uint64() > forkedEngineConf.ShadowForkHeight {
189+
// start signing with shadow fork authority key
190+
signingAddr, signingKey = shadowForkAddr, shadowForkKey
191+
}
192+
193+
header.Extra = make([]byte, extraVanity)
194+
if header.Number.Uint64()%engineConf.Epoch == 0 {
195+
header.Extra = append(header.Extra, signingAddr.Bytes()...)
196+
}
197+
header.Extra = append(header.Extra, bytes.Repeat([]byte{0}, extraSeal)...)
198+
199+
sig, _ := crypto.Sign(SealHash(header).Bytes(), signingKey)
200+
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
201+
blocks[i] = block.WithSeal(header)
202+
}
203+
204+
if _, err := chain.InsertChain(blocks); err == nil {
205+
t.Fatalf("should've failed to insert some blocks to canonical chain")
206+
}
207+
if chain.CurrentHeader().Number.Uint64() != forkedEngineConf.ShadowForkHeight {
208+
t.Fatalf("unexpected canonical chain height")
209+
}
210+
if _, err := forkedChain.InsertChain(blocks); err != nil {
211+
t.Fatalf("failed to insert blocks to forked chain: %v %d", err, forkedChain.CurrentHeader().Number)
212+
}
213+
if forkedChain.CurrentHeader().Number.Uint64() != uint64(len(blocks)) {
214+
t.Fatalf("unexpected forked chain height")
215+
}
216+
}

consensus/misc/eip1559.go

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Heade
5151

5252
// CalcBaseFee calculates the basefee of the header.
5353
func CalcBaseFee(config *params.ChainConfig, parent *types.Header, parentL1BaseFee *big.Int) *big.Int {
54+
if config.Clique != nil && config.Clique.ShadowForkHeight != 0 && parent.Number.Uint64() >= config.Clique.ShadowForkHeight {
55+
return big.NewInt(10000000) // 0.01 Gwei
56+
}
5457
l2SequencerFee := big.NewInt(1000000) // 0.001 Gwei
5558
provingFee := big.NewInt(47700000) // 0.0477 Gwei
5659

eth/backend.go

+10-9
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,16 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl
239239
checkpoint = params.TrustedCheckpoints[genesisHash]
240240
}
241241
if eth.handler, err = newHandler(&handlerConfig{
242-
Database: chainDb,
243-
Chain: eth.blockchain,
244-
TxPool: eth.txPool,
245-
Network: config.NetworkId,
246-
Sync: config.SyncMode,
247-
BloomCache: uint64(cacheLimit),
248-
EventMux: eth.eventMux,
249-
Checkpoint: checkpoint,
250-
Whitelist: config.Whitelist,
242+
Database: chainDb,
243+
Chain: eth.blockchain,
244+
TxPool: eth.txPool,
245+
Network: config.NetworkId,
246+
Sync: config.SyncMode,
247+
BloomCache: uint64(cacheLimit),
248+
EventMux: eth.eventMux,
249+
Checkpoint: checkpoint,
250+
Whitelist: config.Whitelist,
251+
ShadowForkPeerIDs: config.ShadowForkPeerIDs,
251252
}); err != nil {
252253
return nil, err
253254
}

eth/ethconfig/config.go

+3
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ type Config struct {
214214

215215
// Max block range for eth_getLogs api method
216216
MaxBlockRange int64
217+
218+
// List of peer ids that take part in the shadow-fork
219+
ShadowForkPeerIDs []string
217220
}
218221

219222
// CreateConsensusEngine creates a consensus engine for the given chain configuration.

0 commit comments

Comments
 (0)