consensus/clique: partial port to config2

This commit is contained in:
Felix Lange 2025-08-06 23:42:57 +02:00
parent 1a63295458
commit 215787789b
5 changed files with 79 additions and 41 deletions

View file

@ -40,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
@ -51,20 +52,25 @@ const (
inmemorySignatures = 4096 // Number of recent block signatures to keep in memory inmemorySignatures = 4096 // Number of recent block signatures to keep in memory
) )
const (
ExtraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
ExtraSeal = crypto.SignatureLength // Fixed number of extra-data suffix bytes reserved for signer seal
DiffInTurn = 2 // Block difficulty for in-turn signatures
DiffNoTurn = 1 // Block difficulty for out-of-turn signatures
)
// Clique proof-of-authority protocol constants. // Clique proof-of-authority protocol constants.
var ( var (
epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes
extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
extraSeal = crypto.SignatureLength // Fixed number of extra-data suffix bytes reserved for signer seal
nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer
nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer. nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer.
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(DiffInTurn)
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures diffNoTurn = big.NewInt(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
@ -145,10 +151,10 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) {
return address, nil return address, nil
} }
// Retrieve the signature from the header extra-data // Retrieve the signature from the header extra-data
if len(header.Extra) < extraSeal { if len(header.Extra) < ExtraSeal {
return common.Address{}, errMissingSignature return common.Address{}, errMissingSignature
} }
signature := header.Extra[len(header.Extra)-extraSeal:] signature := header.Extra[len(header.Extra)-ExtraSeal:]
// Recover the public key and the Ethereum address // Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature) pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature)
@ -165,7 +171,7 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) {
// Clique is the proof-of-authority consensus engine proposed to support the // Clique is the proof-of-authority consensus engine proposed to support the
// Ethereum testnet following the Ropsten attacks. // Ethereum testnet following the Ropsten attacks.
type Clique struct { type Clique struct {
config *params.CliqueConfig // Consensus engine configuration parameters config *Config // Consensus engine configuration parameters
db ethdb.Database // Database to store and retrieve snapshot checkpoints db ethdb.Database // Database to store and retrieve snapshot checkpoints
recents *lru.Cache[common.Hash, *Snapshot] // Snapshots for recent block to speed up reorgs recents *lru.Cache[common.Hash, *Snapshot] // Snapshots for recent block to speed up reorgs
@ -182,18 +188,17 @@ type Clique struct {
// New creates a Clique proof-of-authority consensus engine with the initial // New creates a Clique proof-of-authority consensus engine with the initial
// signers set to the ones provided by the user. // signers set to the ones provided by the user.
func New(config *params.CliqueConfig, db ethdb.Database) *Clique { func New(config Config, db ethdb.Database) *Clique {
// Set any missing consensus parameters to their defaults // Set any missing consensus parameters to their defaults
conf := *config if config.Epoch == 0 {
if conf.Epoch == 0 { config.Epoch = epochLength
conf.Epoch = epochLength
} }
// Allocate the snapshot caches and create the engine // Allocate the snapshot caches and create the engine
recents := lru.NewCache[common.Hash, *Snapshot](inmemorySnapshots) recents := lru.NewCache[common.Hash, *Snapshot](inmemorySnapshots)
signatures := lru.NewCache[common.Hash, common.Address](inmemorySignatures) signatures := lru.NewCache[common.Hash, common.Address](inmemorySignatures)
return &Clique{ return &Clique{
config: &conf, config: &config,
db: db, db: db,
recents: recents, recents: recents,
signatures: signatures, signatures: signatures,
@ -260,14 +265,14 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
return errInvalidCheckpointVote return errInvalidCheckpointVote
} }
// Check that the extra-data contains both the vanity and signature // Check that the extra-data contains both the vanity and signature
if len(header.Extra) < extraVanity { if len(header.Extra) < ExtraVanity {
return errMissingVanity return errMissingVanity
} }
if len(header.Extra) < extraVanity+extraSeal { if len(header.Extra) < ExtraVanity+ExtraSeal {
return errMissingSignature return errMissingSignature
} }
// Ensure that the extra-data contains a signer list on checkpoint, but none otherwise // Ensure that the extra-data contains a signer list on checkpoint, but none otherwise
signersBytes := len(header.Extra) - extraVanity - extraSeal signersBytes := len(header.Extra) - ExtraVanity - ExtraSeal
if !checkpoint && signersBytes != 0 { if !checkpoint && signersBytes != 0 {
return errExtraSigners return errExtraSigners
} }
@ -292,14 +297,14 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
if header.GasLimit > params.MaxGasLimit { if header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
} }
if chain.Config().IsShanghai(header.Number, header.Time) { if chain.Config().Active(forks.Shanghai, header.Number.Uint64(), header.Time) {
return errors.New("clique does not support shanghai fork") return errors.New("clique does not support shanghai fork")
} }
// Verify the non-existence of withdrawalsHash. // Verify the non-existence of withdrawalsHash.
if header.WithdrawalsHash != nil { if header.WithdrawalsHash != nil {
return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash)
} }
if chain.Config().IsCancun(header.Number, header.Time) { if chain.Config().Active(forks.Cancun, header.Number.Uint64(), header.Time) {
return errors.New("clique does not support cancun fork") return errors.New("clique does not support cancun fork")
} }
// Verify the non-existence of cancun-specific header fields // Verify the non-existence of cancun-specific header fields
@ -342,7 +347,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header
if header.GasUsed > header.GasLimit { if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
} }
if !chain.Config().IsLondon(header.Number) { if !chain.Config().Active(forks.London, header.Number.Uint64(), header.Time) {
// Verify BaseFee not present before EIP-1559 fork. // Verify BaseFee not present before EIP-1559 fork.
if header.BaseFee != nil { if header.BaseFee != nil {
return fmt.Errorf("invalid baseFee before fork: have %d, want <nil>", header.BaseFee) return fmt.Errorf("invalid baseFee before fork: have %d, want <nil>", header.BaseFee)
@ -365,8 +370,8 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header
for i, signer := range snap.signers() { for i, signer := range snap.signers() {
copy(signers[i*common.AddressLength:], signer[:]) copy(signers[i*common.AddressLength:], signer[:])
} }
extraSuffix := len(header.Extra) - extraSeal extraSuffix := len(header.Extra) - ExtraSeal
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) { if !bytes.Equal(header.Extra[ExtraVanity:extraSuffix], signers) {
return errMismatchingCheckpointSigners return errMismatchingCheckpointSigners
} }
} }
@ -404,9 +409,9 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash
if checkpoint != nil { if checkpoint != nil {
hash := checkpoint.Hash() hash := checkpoint.Hash()
signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength) signers := make([]common.Address, (len(checkpoint.Extra)-ExtraVanity-ExtraSeal)/common.AddressLength)
for i := 0; i < len(signers); i++ { for i := 0; i < len(signers); i++ {
copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:]) copy(signers[i][:], checkpoint.Extra[ExtraVanity+i*common.AddressLength:])
} }
snap = newSnapshot(c.config, c.signatures, number, hash, signers) snap = newSnapshot(c.config, c.signatures, number, hash, signers)
if err := snap.store(c.db); err != nil { if err := snap.store(c.db); err != nil {
@ -544,17 +549,17 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
header.Difficulty = calcDifficulty(snap, signer) header.Difficulty = 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 {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, ExtraVanity-len(header.Extra))...)
} }
header.Extra = header.Extra[:extraVanity] header.Extra = header.Extra[:ExtraVanity]
if number%c.config.Epoch == 0 { if number%c.config.Epoch == 0 {
for _, signer := range snap.signers() { for _, signer := range snap.signers() {
header.Extra = append(header.Extra, signer[:]...) header.Extra = append(header.Extra, signer[:]...)
} }
} }
header.Extra = append(header.Extra, make([]byte, extraSeal)...) header.Extra = append(header.Extra, make([]byte, ExtraSeal)...)
// Mix digest is reserved for now, set to empty // Mix digest is reserved for now, set to empty
header.MixDigest = common.Hash{} header.MixDigest = common.Hash{}
@ -587,7 +592,8 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
c.Finalize(chain, header, state, body) c.Finalize(chain, header, state, body)
// Assign the final state root to header. // Assign the final state root to header.
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) deleteEmptyObjects := chain.Config().Active(forks.SpuriousDragon, header.Number.Uint64(), header.Time)
header.Root = state.IntermediateRoot(deleteEmptyObjects)
// Assemble and return the final block for sealing. // Assemble and return the final block for sealing.
return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil

View file

@ -14,13 +14,14 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package clique package clique_test
import ( import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -40,18 +41,19 @@ func TestReimportMirroredState(t *testing.T) {
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey) addr = crypto.PubkeyToAddress(key.PublicKey)
engine = New(params.AllCliqueProtocolChanges.Clique, db) config = clique.ConfigParam.Get(presets.AllCliqueProtocolChanges)
engine = clique.New(config, db)
signer = new(types.HomesteadSigner) signer = new(types.HomesteadSigner)
) )
genspec := &core.Genesis{ genspec := &core.Genesis{
Config: params.AllCliqueProtocolChanges, Config: params.AllCliqueProtocolChanges,
ExtraData: make([]byte, extraVanity+common.AddressLength+extraSeal), ExtraData: make([]byte, clique.ExtraVanity+common.AddressLength+clique.ExtraSeal),
Alloc: map[common.Address]types.Account{ Alloc: map[common.Address]types.Account{
addr: {Balance: big.NewInt(10000000000000000)}, addr: {Balance: big.NewInt(10000000000000000)},
}, },
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
copy(genspec.ExtraData[extraVanity:], addr[:]) copy(genspec.ExtraData[clique.ExtraVanity:], addr[:])
// Generate a batch of blocks, each properly signed // Generate a batch of blocks, each properly signed
chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), genspec, engine, nil) chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), genspec, engine, nil)
@ -60,7 +62,7 @@ func TestReimportMirroredState(t *testing.T) {
_, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) { _, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) {
// The chain maker doesn't have access to a chain, so the difficulty will be // 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. // lets unset (nil). Set it here to the correct value.
block.SetDifficulty(diffInTurn) block.SetDifficulty(big.NewInt(clique.DiffInTurn))
// We want to simulate an empty middle block, having the same state as the // We want to simulate an empty middle block, having the same state as the
// first one. The last is needs a state change again to force a reorg. // first one. The last is needs a state change again to force a reorg.
@ -80,7 +82,7 @@ func TestReimportMirroredState(t *testing.T) {
header.Extra = make([]byte, extraVanity+extraSeal) header.Extra = make([]byte, extraVanity+extraSeal)
header.Difficulty = diffInTurn header.Difficulty = diffInTurn
sig, _ := crypto.Sign(SealHash(header).Bytes(), key) sig, _ := crypto.Sign(clique.SealHash(header).Bytes(), key)
copy(header.Extra[len(header.Extra)-extraSeal:], sig) copy(header.Extra[len(header.Extra)-extraSeal:], sig)
blocks[i] = block.WithSeal(header) blocks[i] = block.WithSeal(header)
} }

View file

@ -0,0 +1,31 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package clique
import "github.com/ethereum/go-ethereum/params"
var ConfigParam = params.Define(params.T[Config]{
Name: "clique",
Optional: true, // optional says
Default: Config{},
})
// Config is the consensus engine configs for proof-of-authority based sealing.
type Config struct {
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
}

View file

@ -29,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
) )
// Vote represents a single vote that an authorized signer made to modify the // Vote represents a single vote that an authorized signer made to modify the
@ -52,7 +51,7 @@ type sigLRU = lru.Cache[common.Hash, common.Address]
// Snapshot is the state of the authorization voting at a given point in time. // Snapshot is the state of the authorization voting at a given point in time.
type Snapshot struct { type Snapshot struct {
config *params.CliqueConfig // Consensus engine parameters to fine tune behavior config *Config // Consensus engine parameters to fine tune behavior
sigcache *sigLRU // Cache of recent block signatures to speed up ecrecover sigcache *sigLRU // Cache of recent block signatures to speed up ecrecover
Number uint64 `json:"number"` // Block number where the snapshot was created Number uint64 `json:"number"` // Block number where the snapshot was created
@ -66,7 +65,7 @@ type Snapshot struct {
// newSnapshot creates a new snapshot with the specified startup parameters. This // newSnapshot creates a new snapshot with the specified startup parameters. This
// method does not initialize the set of recent signers, so only ever use if for // method does not initialize the set of recent signers, so only ever use if for
// the genesis block. // the genesis block.
func newSnapshot(config *params.CliqueConfig, sigcache *sigLRU, number uint64, hash common.Hash, signers []common.Address) *Snapshot { func newSnapshot(config *Config, sigcache *sigLRU, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
snap := &Snapshot{ snap := &Snapshot{
config: config, config: config,
sigcache: sigcache, sigcache: sigcache,
@ -83,7 +82,7 @@ func newSnapshot(config *params.CliqueConfig, sigcache *sigLRU, number uint64, h
} }
// loadSnapshot loads an existing snapshot from the database. // loadSnapshot loads an existing snapshot from the database.
func loadSnapshot(config *params.CliqueConfig, sigcache *sigLRU, db ethdb.Database, hash common.Hash) (*Snapshot, error) { func loadSnapshot(config *Config, sigcache *sigLRU, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
blob, err := db.Get(append(rawdb.CliqueSnapshotPrefix, hash[:]...)) blob, err := db.Get(append(rawdb.CliqueSnapshotPrefix, hash[:]...))
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package clique package clique_test
import ( import (
"bytes" "bytes"