Skip to content

Commit

Permalink
feat(clique): allow shadowforking a clique network (#828)
Browse files Browse the repository at this point in the history
  • Loading branch information
omerfirmak authored Aug 1, 2024
1 parent 9abdd5b commit 51c7eee
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 48 deletions.
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ var (
utils.L1DeploymentBlockFlag,
utils.CircuitCapacityCheckEnabledFlag,
utils.RollupVerifyEnabledFlag,
utils.ShadowforkPeersFlag,
}

rpcFlags = []cli.Flag{
Expand Down
1 change: 1 addition & 0 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.BloomFilterSizeFlag,
cli.HelpFlag,
utils.CatalystFlag,
utils.ShadowforkPeersFlag,
},
},
}
Expand Down
10 changes: 10 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,12 @@ var (
Name: "rpc.getlogs.maxrange",
Usage: "Limit max fetched block range for `eth_getLogs` method",
}

// Shadowfork peers
ShadowforkPeersFlag = cli.StringSliceFlag{
Name: "net.shadowforkpeers",
Usage: "peer ids of shadow fork peers",
}
)

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

// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()
Expand Down
33 changes: 22 additions & 11 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ var (

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

diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures
diffShadowFork = diffNoTurn
)

// Various error messages to mark blocks invalid. These should be private to
Expand Down Expand Up @@ -195,6 +196,7 @@ func New(config *params.CliqueConfig, db ethdb.Database) *Clique {
if conf.Epoch == 0 {
conf.Epoch = epochLength
}

// Allocate the snapshot caches and create the engine
recents, _ := lru.NewARC(inmemorySnapshots)
signatures, _ := lru.NewARC(inmemorySignatures)
Expand Down Expand Up @@ -291,7 +293,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
}
// Ensure that the block's difficulty is meaningful (may not be correct at this point)
if number > 0 {
if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0) {
if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0 && header.Difficulty.Cmp(diffShadowFork) != 0) {
return errInvalidDifficulty
}
}
Expand Down Expand Up @@ -375,6 +377,14 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash
snap *Snapshot
)
for snap == nil {
if c.config.ShadowForkHeight > 0 && number == c.config.ShadowForkHeight {
c.signatures.Purge()
c.recents.Purge()
c.proposals = make(map[common.Address]bool)
snap = newSnapshot(c.config, c.signatures, number, hash, []common.Address{c.config.ShadowForkSigner})
break
}

// If an in-memory snapshot was found, use that
if s, ok := c.recents.Get(hash); ok {
snap = s.(*Snapshot)
Expand Down Expand Up @@ -485,11 +495,8 @@ func (c *Clique) verifySeal(snap *Snapshot, header *types.Header, parents []*typ
}
// Ensure that the difficulty corresponds to the turn-ness of the signer
if !c.fakeDiff {
inturn := snap.inturn(header.Number.Uint64(), signer)
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
return errWrongDifficulty
}
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
expected := c.calcDifficulty(snap, signer)
if header.Difficulty.Cmp(expected) != 0 {
return errWrongDifficulty
}
}
Expand Down Expand Up @@ -534,7 +541,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
c.lock.RUnlock()

// Set the correct difficulty
header.Difficulty = calcDifficulty(snap, signer)
header.Difficulty = c.calcDifficulty(snap, signer)

// Ensure the extra data has all its components
if len(header.Extra) < extraVanity {
Expand Down Expand Up @@ -678,10 +685,14 @@ func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64,
c.lock.RLock()
signer := c.signer
c.lock.RUnlock()
return calcDifficulty(snap, signer)
return c.calcDifficulty(snap, signer)
}

func calcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
func (c *Clique) calcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
if c.config.ShadowForkHeight > 0 && snap.Number >= c.config.ShadowForkHeight {
// if we are past shadow fork point, set a low difficulty so that mainnet nodes don't try to switch to forked chain
return new(big.Int).Set(diffShadowFork)
}
if snap.inturn(snap.Number+1, signer) {
return new(big.Int).Set(diffInTurn)
}
Expand Down
89 changes: 89 additions & 0 deletions consensus/clique/clique_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package clique

import (
"bytes"
"math/big"
"strings"
"testing"

"github.com/scroll-tech/go-ethereum/common"
Expand Down Expand Up @@ -125,3 +127,90 @@ func TestSealHash(t *testing.T) {
t.Errorf("have %x, want %x", have, want)
}
}

func TestShadowFork(t *testing.T) {
engineConf := *params.AllCliqueProtocolChanges.Clique
engineConf.Epoch = 2
forkedEngineConf := engineConf
forkedEngineConf.ShadowForkHeight = 3
shadowForkKey, _ := crypto.HexToECDSA(strings.Repeat("11", 32))
shadowForkAddr := crypto.PubkeyToAddress(shadowForkKey.PublicKey)
forkedEngineConf.ShadowForkSigner = shadowForkAddr

// Initialize a Clique chain with a single signer
var (
db = rawdb.NewMemoryDatabase()
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
engine = New(&engineConf, db)
signer = new(types.HomesteadSigner)
forkedEngine = New(&forkedEngineConf, db)
)
genspec := &core.Genesis{
ExtraData: make([]byte, extraVanity+common.AddressLength+extraSeal),
Alloc: map[common.Address]core.GenesisAccount{
addr: {Balance: big.NewInt(10000000000000000)},
},
BaseFee: big.NewInt(params.InitialBaseFee),
}
copy(genspec.ExtraData[extraVanity:], addr[:])
genesis := genspec.MustCommit(db)

// Generate a batch of blocks, each properly signed
chain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil)
defer chain.Stop()

forkedChain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, forkedEngine, vm.Config{}, nil, nil)
defer forkedChain.Stop()

blocks, _ := core.GenerateChain(params.AllCliqueProtocolChanges, genesis, forkedEngine, db, 16, func(i int, block *core.BlockGen) {
// The chain maker doesn't have access to a chain, so the difficulty will be
// lets unset (nil). Set it here to the correct value.
if block.Number().Uint64() > forkedEngineConf.ShadowForkHeight {
block.SetDifficulty(diffShadowFork)
} else {
block.SetDifficulty(diffInTurn)
}

tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr), common.Address{0x00}, new(big.Int), params.TxGas, block.BaseFee(), nil), signer, key)
if err != nil {
panic(err)
}
block.AddTxWithChain(chain, tx)
})
for i, block := range blocks {
header := block.Header()
if i > 0 {
header.ParentHash = blocks[i-1].Hash()
}

signingAddr, signingKey := addr, key
if header.Number.Uint64() > forkedEngineConf.ShadowForkHeight {
// start signing with shadow fork authority key
signingAddr, signingKey = shadowForkAddr, shadowForkKey
}

header.Extra = make([]byte, extraVanity)
if header.Number.Uint64()%engineConf.Epoch == 0 {
header.Extra = append(header.Extra, signingAddr.Bytes()...)
}
header.Extra = append(header.Extra, bytes.Repeat([]byte{0}, extraSeal)...)

sig, _ := crypto.Sign(SealHash(header).Bytes(), signingKey)
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
blocks[i] = block.WithSeal(header)
}

if _, err := chain.InsertChain(blocks); err == nil {
t.Fatalf("should've failed to insert some blocks to canonical chain")
}
if chain.CurrentHeader().Number.Uint64() != forkedEngineConf.ShadowForkHeight {
t.Fatalf("unexpected canonical chain height")
}
if _, err := forkedChain.InsertChain(blocks); err != nil {
t.Fatalf("failed to insert blocks to forked chain: %v %d", err, forkedChain.CurrentHeader().Number)
}
if forkedChain.CurrentHeader().Number.Uint64() != uint64(len(blocks)) {
t.Fatalf("unexpected forked chain height")
}
}
3 changes: 3 additions & 0 deletions consensus/misc/eip1559.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Heade

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

Expand Down
19 changes: 10 additions & 9 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,16 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl
checkpoint = params.TrustedCheckpoints[genesisHash]
}
if eth.handler, err = newHandler(&handlerConfig{
Database: chainDb,
Chain: eth.blockchain,
TxPool: eth.txPool,
Network: config.NetworkId,
Sync: config.SyncMode,
BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux,
Checkpoint: checkpoint,
Whitelist: config.Whitelist,
Database: chainDb,
Chain: eth.blockchain,
TxPool: eth.txPool,
Network: config.NetworkId,
Sync: config.SyncMode,
BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux,
Checkpoint: checkpoint,
Whitelist: config.Whitelist,
ShadowForkPeerIDs: config.ShadowForkPeerIDs,
}); err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ type Config struct {

// Max block range for eth_getLogs api method
MaxBlockRange int64

// List of peer ids that take part in the shadow-fork
ShadowForkPeerIDs []string
}

// CreateConsensusEngine creates a consensus engine for the given chain configuration.
Expand Down
Loading

0 comments on commit 51c7eee

Please sign in to comment.