Skip to content

feat(clique): allow shadowforking a clique network #828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading