mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 07:06:42 +00:00
feat(clique): allow shadowforking a clique network (#828)
This commit is contained in:
parent
9abdd5b9aa
commit
51c7eee1e4
14 changed files with 292 additions and 48 deletions
|
|
@ -169,6 +169,7 @@ var (
|
||||||
utils.L1DeploymentBlockFlag,
|
utils.L1DeploymentBlockFlag,
|
||||||
utils.CircuitCapacityCheckEnabledFlag,
|
utils.CircuitCapacityCheckEnabledFlag,
|
||||||
utils.RollupVerifyEnabledFlag,
|
utils.RollupVerifyEnabledFlag,
|
||||||
|
utils.ShadowforkPeersFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
rpcFlags = []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
|
||||||
utils.BloomFilterSizeFlag,
|
utils.BloomFilterSizeFlag,
|
||||||
cli.HelpFlag,
|
cli.HelpFlag,
|
||||||
utils.CatalystFlag,
|
utils.CatalystFlag,
|
||||||
|
utils.ShadowforkPeersFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -858,6 +858,12 @@ var (
|
||||||
Name: "rpc.getlogs.maxrange",
|
Name: "rpc.getlogs.maxrange",
|
||||||
Usage: "Limit max fetched block range for `eth_getLogs` method",
|
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
|
// MakeDataDir retrieves the currently requested data directory, terminating
|
||||||
|
|
@ -1651,6 +1657,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
setCircuitCapacityCheck(ctx, cfg)
|
setCircuitCapacityCheck(ctx, cfg)
|
||||||
setEnableRollupVerify(ctx, cfg)
|
setEnableRollupVerify(ctx, cfg)
|
||||||
setMaxBlockRange(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
|
// Cap the cache allowance and tune the garbage collector
|
||||||
mem, err := gopsutil.VirtualMemory()
|
mem, err := gopsutil.VirtualMemory()
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,9 @@ var (
|
||||||
|
|
||||||
uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
|
uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
|
||||||
|
|
||||||
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
|
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
|
||||||
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-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
|
// 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 {
|
||||||
if conf.Epoch == 0 {
|
if conf.Epoch == 0 {
|
||||||
conf.Epoch = epochLength
|
conf.Epoch = epochLength
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate the snapshot caches and create the engine
|
// Allocate the snapshot caches and create the engine
|
||||||
recents, _ := lru.NewARC(inmemorySnapshots)
|
recents, _ := lru.NewARC(inmemorySnapshots)
|
||||||
signatures, _ := lru.NewARC(inmemorySignatures)
|
signatures, _ := lru.NewARC(inmemorySignatures)
|
||||||
|
|
@ -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)
|
// Ensure that the block's difficulty is meaningful (may not be correct at this point)
|
||||||
if number > 0 {
|
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
|
return errInvalidDifficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -375,6 +377,14 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash
|
||||||
snap *Snapshot
|
snap *Snapshot
|
||||||
)
|
)
|
||||||
for snap == nil {
|
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 an in-memory snapshot was found, use that
|
||||||
if s, ok := c.recents.Get(hash); ok {
|
if s, ok := c.recents.Get(hash); ok {
|
||||||
snap = s.(*Snapshot)
|
snap = s.(*Snapshot)
|
||||||
|
|
@ -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
|
// Ensure that the difficulty corresponds to the turn-ness of the signer
|
||||||
if !c.fakeDiff {
|
if !c.fakeDiff {
|
||||||
inturn := snap.inturn(header.Number.Uint64(), signer)
|
expected := c.calcDifficulty(snap, signer)
|
||||||
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
|
if header.Difficulty.Cmp(expected) != 0 {
|
||||||
return errWrongDifficulty
|
|
||||||
}
|
|
||||||
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
|
|
||||||
return errWrongDifficulty
|
return errWrongDifficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -534,7 +541,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
|
||||||
c.lock.RUnlock()
|
c.lock.RUnlock()
|
||||||
|
|
||||||
// Set the correct difficulty
|
// Set the correct difficulty
|
||||||
header.Difficulty = calcDifficulty(snap, signer)
|
header.Difficulty = c.calcDifficulty(snap, signer)
|
||||||
|
|
||||||
// Ensure the extra data has all its components
|
// Ensure the extra data has all its components
|
||||||
if len(header.Extra) < extraVanity {
|
if len(header.Extra) < extraVanity {
|
||||||
|
|
@ -678,10 +685,14 @@ func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64,
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
signer := c.signer
|
signer := c.signer
|
||||||
c.lock.RUnlock()
|
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) {
|
if snap.inturn(snap.Number+1, signer) {
|
||||||
return new(big.Int).Set(diffInTurn)
|
return new(big.Int).Set(diffInTurn)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@
|
||||||
package clique
|
package clique
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"github.com/scroll-tech/go-ethereum/common"
|
||||||
|
|
@ -125,3 +127,90 @@ func TestSealHash(t *testing.T) {
|
||||||
t.Errorf("have %x, want %x", have, want)
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Heade
|
||||||
|
|
||||||
// CalcBaseFee calculates the basefee of the header.
|
// CalcBaseFee calculates the basefee of the header.
|
||||||
func CalcBaseFee(config *params.ChainConfig, parent *types.Header, parentL1BaseFee *big.Int) *big.Int {
|
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
|
l2SequencerFee := big.NewInt(1000000) // 0.001 Gwei
|
||||||
provingFee := big.NewInt(47700000) // 0.0477 Gwei
|
provingFee := big.NewInt(47700000) // 0.0477 Gwei
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,15 +239,16 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl
|
||||||
checkpoint = params.TrustedCheckpoints[genesisHash]
|
checkpoint = params.TrustedCheckpoints[genesisHash]
|
||||||
}
|
}
|
||||||
if eth.handler, err = newHandler(&handlerConfig{
|
if eth.handler, err = newHandler(&handlerConfig{
|
||||||
Database: chainDb,
|
Database: chainDb,
|
||||||
Chain: eth.blockchain,
|
Chain: eth.blockchain,
|
||||||
TxPool: eth.txPool,
|
TxPool: eth.txPool,
|
||||||
Network: config.NetworkId,
|
Network: config.NetworkId,
|
||||||
Sync: config.SyncMode,
|
Sync: config.SyncMode,
|
||||||
BloomCache: uint64(cacheLimit),
|
BloomCache: uint64(cacheLimit),
|
||||||
EventMux: eth.eventMux,
|
EventMux: eth.eventMux,
|
||||||
Checkpoint: checkpoint,
|
Checkpoint: checkpoint,
|
||||||
Whitelist: config.Whitelist,
|
Whitelist: config.Whitelist,
|
||||||
|
ShadowForkPeerIDs: config.ShadowForkPeerIDs,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,9 @@ type Config struct {
|
||||||
|
|
||||||
// Max block range for eth_getLogs api method
|
// Max block range for eth_getLogs api method
|
||||||
MaxBlockRange int64
|
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.
|
// CreateConsensusEngine creates a consensus engine for the given chain configuration.
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -76,15 +77,16 @@ type txPool interface {
|
||||||
// handlerConfig is the collection of initialization parameters to create a full
|
// handlerConfig is the collection of initialization parameters to create a full
|
||||||
// node network handler.
|
// node network handler.
|
||||||
type handlerConfig struct {
|
type handlerConfig struct {
|
||||||
Database ethdb.Database // Database for direct sync insertions
|
Database ethdb.Database // Database for direct sync insertions
|
||||||
Chain *core.BlockChain // Blockchain to serve data from
|
Chain *core.BlockChain // Blockchain to serve data from
|
||||||
TxPool txPool // Transaction pool to propagate from
|
TxPool txPool // Transaction pool to propagate from
|
||||||
Network uint64 // Network identifier to adfvertise
|
Network uint64 // Network identifier to adfvertise
|
||||||
Sync downloader.SyncMode // Whether to fast or full sync
|
Sync downloader.SyncMode // Whether to fast or full sync
|
||||||
BloomCache uint64 // Megabytes to alloc for fast sync bloom
|
BloomCache uint64 // Megabytes to alloc for fast sync bloom
|
||||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||||
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
|
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
|
||||||
Whitelist map[uint64]common.Hash // Hard coded whitelist for sync challenged
|
Whitelist map[uint64]common.Hash // Hard coded whitelist for sync challenged
|
||||||
|
ShadowForkPeerIDs []string // List of peer ids that take part in the shadow-fork
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
|
|
@ -122,6 +124,8 @@ type handler struct {
|
||||||
chainSync *chainSyncer
|
chainSync *chainSyncer
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
peerWG sync.WaitGroup
|
peerWG sync.WaitGroup
|
||||||
|
|
||||||
|
shadowForkPeerIDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHandler returns a handler for all Ethereum chain management protocol.
|
// newHandler returns a handler for all Ethereum chain management protocol.
|
||||||
|
|
@ -131,15 +135,16 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
config.EventMux = new(event.TypeMux) // Nicety initialization for tests
|
config.EventMux = new(event.TypeMux) // Nicety initialization for tests
|
||||||
}
|
}
|
||||||
h := &handler{
|
h := &handler{
|
||||||
networkID: config.Network,
|
networkID: config.Network,
|
||||||
forkFilter: forkid.NewFilter(config.Chain),
|
forkFilter: forkid.NewFilter(config.Chain),
|
||||||
eventMux: config.EventMux,
|
eventMux: config.EventMux,
|
||||||
database: config.Database,
|
database: config.Database,
|
||||||
txpool: config.TxPool,
|
txpool: config.TxPool,
|
||||||
chain: config.Chain,
|
chain: config.Chain,
|
||||||
peers: newPeerSet(),
|
peers: newPeerSet(),
|
||||||
whitelist: config.Whitelist,
|
whitelist: config.Whitelist,
|
||||||
quitSync: make(chan struct{}),
|
quitSync: make(chan struct{}),
|
||||||
|
shadowForkPeerIDs: config.ShadowForkPeerIDs,
|
||||||
}
|
}
|
||||||
if config.Sync == downloader.FullSync {
|
if config.Sync == downloader.FullSync {
|
||||||
// The database seems empty as the current block is the genesis. Yet the fast
|
// The database seems empty as the current block is the genesis. Yet the fast
|
||||||
|
|
@ -182,6 +187,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
if atomic.LoadUint32(&h.fastSync) == 1 && atomic.LoadUint32(&h.snapSync) == 0 {
|
if atomic.LoadUint32(&h.fastSync) == 1 && atomic.LoadUint32(&h.snapSync) == 0 {
|
||||||
h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database)
|
h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer)
|
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer)
|
||||||
|
|
||||||
// Construct the fetcher (short sync)
|
// Construct the fetcher (short sync)
|
||||||
|
|
@ -217,7 +223,13 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
}
|
}
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer)
|
|
||||||
|
fetcherDropPeerFunc := h.removePeer
|
||||||
|
// If we are shadowforking, don't drop peers.
|
||||||
|
if config.ShadowForkPeerIDs != nil {
|
||||||
|
fetcherDropPeerFunc = func(id string) {}
|
||||||
|
}
|
||||||
|
h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, fetcherDropPeerFunc)
|
||||||
|
|
||||||
fetchTx := func(peer string, hashes []common.Hash) error {
|
fetchTx := func(peer string, hashes []common.Hash) error {
|
||||||
p := h.peers.peer(peer)
|
p := h.peers.peer(peer)
|
||||||
|
|
@ -306,7 +318,9 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
|
||||||
|
|
||||||
// Propagate existing transactions. new transactions appearing
|
// Propagate existing transactions. new transactions appearing
|
||||||
// after this will be sent via broadcasts.
|
// after this will be sent via broadcasts.
|
||||||
h.syncTransactions(peer)
|
if h.shadowForkPeerIDs == nil || slices.Contains(h.shadowForkPeerIDs, peer.ID()) {
|
||||||
|
h.syncTransactions(peer)
|
||||||
|
}
|
||||||
|
|
||||||
// If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse)
|
// If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse)
|
||||||
if h.checkpointHash != (common.Hash{}) {
|
if h.checkpointHash != (common.Hash{}) {
|
||||||
|
|
@ -433,7 +447,7 @@ func (h *handler) Stop() {
|
||||||
// will only announce its availability (depending what's requested).
|
// will only announce its availability (depending what's requested).
|
||||||
func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
|
func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
|
||||||
hash := block.Hash()
|
hash := block.Hash()
|
||||||
peers := h.peers.peersWithoutBlock(hash)
|
peers := onlyShadowForkPeers(h.shadowForkPeerIDs, h.peers.peersWithoutBlock(hash))
|
||||||
|
|
||||||
// If propagation is requested, send to a subset of the peer
|
// If propagation is requested, send to a subset of the peer
|
||||||
if propagate {
|
if propagate {
|
||||||
|
|
@ -483,7 +497,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
||||||
if tx.IsL1MessageTx() {
|
if tx.IsL1MessageTx() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
peers := h.peers.peersWithoutTransaction(tx.Hash())
|
peers := onlyShadowForkPeers(h.shadowForkPeerIDs, h.peers.peersWithoutTransaction(tx.Hash()))
|
||||||
// Send the tx unconditionally to a subset of our peers
|
// Send the tx unconditionally to a subset of our peers
|
||||||
numDirect := int(math.Sqrt(float64(len(peers))))
|
numDirect := int(math.Sqrt(float64(len(peers))))
|
||||||
for _, peer := range peers[:numDirect] {
|
for _, peer := range peers[:numDirect] {
|
||||||
|
|
@ -533,3 +547,16 @@ func (h *handler) txBroadcastLoop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// onlyShadowForkPeers filters out peers that are not part of the shadow fork
|
||||||
|
func onlyShadowForkPeers[peerT interface {
|
||||||
|
ID() string
|
||||||
|
}](shadowForkPeerIDs []string, peers []peerT) []peerT {
|
||||||
|
if shadowForkPeerIDs == nil {
|
||||||
|
return peers
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.DeleteFunc(peers, func(peer peerT) bool {
|
||||||
|
return !slices.Contains(shadowForkPeerIDs, peer.ID())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"github.com/scroll-tech/go-ethereum/common"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus/ethash"
|
"github.com/scroll-tech/go-ethereum/consensus/ethash"
|
||||||
|
|
@ -168,3 +171,82 @@ func (b *testHandler) close() {
|
||||||
b.handler.Stop()
|
b.handler.Stop()
|
||||||
b.chain.Stop()
|
b.chain.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testPeer struct {
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p testPeer) ID() string {
|
||||||
|
return p.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnlyShadowForkPeers(t *testing.T) {
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
shadowForkPeerIDs []string
|
||||||
|
peers []testPeer
|
||||||
|
expectedPeerIDs []string
|
||||||
|
}{
|
||||||
|
"nil peers": {
|
||||||
|
shadowForkPeerIDs: nil,
|
||||||
|
peers: nil,
|
||||||
|
expectedPeerIDs: []string{},
|
||||||
|
},
|
||||||
|
"empty peers": {
|
||||||
|
shadowForkPeerIDs: nil,
|
||||||
|
peers: []testPeer{},
|
||||||
|
expectedPeerIDs: []string{},
|
||||||
|
},
|
||||||
|
"no fork": {
|
||||||
|
shadowForkPeerIDs: nil,
|
||||||
|
peers: []testPeer{
|
||||||
|
{
|
||||||
|
id: "peer1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPeerIDs: []string{
|
||||||
|
"peer1",
|
||||||
|
"peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"some shadow fork peers": {
|
||||||
|
shadowForkPeerIDs: []string{"peer2"},
|
||||||
|
peers: []testPeer{
|
||||||
|
{
|
||||||
|
id: "peer1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPeerIDs: []string{
|
||||||
|
"peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"no shadow fork peers": {
|
||||||
|
shadowForkPeerIDs: []string{"peer2"},
|
||||||
|
peers: []testPeer{
|
||||||
|
{
|
||||||
|
id: "peer1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "peer3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPeerIDs: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for desc, test := range tests {
|
||||||
|
t.Run(desc, func(t *testing.T) {
|
||||||
|
gotIds := []string{}
|
||||||
|
for _, peer := range onlyShadowForkPeers(test.shadowForkPeerIDs, test.peers) {
|
||||||
|
gotIds = append(gotIds, peer.ID())
|
||||||
|
}
|
||||||
|
require.Equal(t, gotIds, test.expectedPeerIDs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package eth
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"github.com/scroll-tech/go-ethereum/common"
|
||||||
|
|
@ -231,7 +232,7 @@ func (ps *peerSet) snapLen() int {
|
||||||
|
|
||||||
// peerWithHighestTD retrieves the known peer with the currently highest total
|
// peerWithHighestTD retrieves the known peer with the currently highest total
|
||||||
// difficulty.
|
// difficulty.
|
||||||
func (ps *peerSet) peerWithHighestTD() *eth.Peer {
|
func (ps *peerSet) peerWithHighestTD(whileList []string) *eth.Peer {
|
||||||
ps.lock.RLock()
|
ps.lock.RLock()
|
||||||
defer ps.lock.RUnlock()
|
defer ps.lock.RUnlock()
|
||||||
|
|
||||||
|
|
@ -240,6 +241,9 @@ func (ps *peerSet) peerWithHighestTD() *eth.Peer {
|
||||||
bestTd *big.Int
|
bestTd *big.Int
|
||||||
)
|
)
|
||||||
for _, p := range ps.peers {
|
for _, p := range ps.peers {
|
||||||
|
if whileList != nil && !slices.Contains(whileList, p.ID()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
|
if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
|
||||||
bestPeer, bestTd = p.Peer, td
|
bestPeer, bestTd = p.Peer, td
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
eth/sync.go
12
eth/sync.go
|
|
@ -156,8 +156,18 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
||||||
if cs.handler.peers.len() < minPeers {
|
if cs.handler.peers.len() < minPeers {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var syncWhiteList []string
|
||||||
|
chainConfig := cs.handler.chain.Config()
|
||||||
|
currentHeight := cs.handler.chain.CurrentHeader().Number.Uint64()
|
||||||
|
if chainConfig.Clique != nil {
|
||||||
|
shadowForkHeight := chainConfig.Clique.ShadowForkHeight
|
||||||
|
if shadowForkHeight != 0 && currentHeight >= shadowForkHeight {
|
||||||
|
syncWhiteList = cs.handler.shadowForkPeerIDs
|
||||||
|
}
|
||||||
|
}
|
||||||
// We have enough peers, check TD
|
// We have enough peers, check TD
|
||||||
peer := cs.handler.peers.peerWithHighestTD()
|
peer := cs.handler.peers.peerWithHighestTD(syncWhiteList)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -718,9 +718,11 @@ func (c *EthashConfig) String() string {
|
||||||
|
|
||||||
// CliqueConfig is the consensus engine configs for proof-of-authority based sealing.
|
// CliqueConfig is the consensus engine configs for proof-of-authority based sealing.
|
||||||
type CliqueConfig struct {
|
type CliqueConfig struct {
|
||||||
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
||||||
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
|
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
|
||||||
RelaxedPeriod bool `json:"relaxed_period"` // Relaxes the period to be just an upper bound
|
RelaxedPeriod bool `json:"relaxed_period"` // Relaxes the period to be just an upper bound
|
||||||
|
ShadowForkHeight uint64 `json:"shadow_fork_height"` // Allows shadow forking consensus layer at given height
|
||||||
|
ShadowForkSigner common.Address `json:"shadow_fork_signer"` // Sets the address to be the authorized signer after the shadow fork
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements the stringer interface, returning the consensus engine details.
|
// String implements the stringer interface, returning the consensus engine details.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 5 // Major version component of the current release
|
VersionMajor = 5 // Major version component of the current release
|
||||||
VersionMinor = 5 // Minor version component of the current release
|
VersionMinor = 5 // Minor version component of the current release
|
||||||
VersionPatch = 18 // Patch version component of the current release
|
VersionPatch = 19 // Patch version component of the current release
|
||||||
VersionMeta = "mainnet" // Version metadata to append to the version string
|
VersionMeta = "mainnet" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue