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/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3"
@ -51,20 +52,25 @@ const (
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.
var (
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
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.
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures
diffInTurn = big.NewInt(DiffInTurn)
diffNoTurn = big.NewInt(DiffNoTurn)
)
// 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
}
// Retrieve the signature from the header extra-data
if len(header.Extra) < extraSeal {
if len(header.Extra) < ExtraSeal {
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
pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature)
@ -165,8 +171,8 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) {
// Clique is the proof-of-authority consensus engine proposed to support the
// Ethereum testnet following the Ropsten attacks.
type Clique struct {
config *params.CliqueConfig // Consensus engine configuration parameters
db ethdb.Database // Database to store and retrieve snapshot checkpoints
config *Config // Consensus engine configuration parameters
db ethdb.Database // Database to store and retrieve snapshot checkpoints
recents *lru.Cache[common.Hash, *Snapshot] // Snapshots for recent block to speed up reorgs
signatures *sigLRU // Signatures of recent blocks to speed up mining
@ -182,18 +188,17 @@ type Clique struct {
// New creates a Clique proof-of-authority consensus engine with the initial
// 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
conf := *config
if conf.Epoch == 0 {
conf.Epoch = epochLength
if config.Epoch == 0 {
config.Epoch = epochLength
}
// Allocate the snapshot caches and create the engine
recents := lru.NewCache[common.Hash, *Snapshot](inmemorySnapshots)
signatures := lru.NewCache[common.Hash, common.Address](inmemorySignatures)
return &Clique{
config: &conf,
config: &config,
db: db,
recents: recents,
signatures: signatures,
@ -260,14 +265,14 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
return errInvalidCheckpointVote
}
// Check that the extra-data contains both the vanity and signature
if len(header.Extra) < extraVanity {
if len(header.Extra) < ExtraVanity {
return errMissingVanity
}
if len(header.Extra) < extraVanity+extraSeal {
if len(header.Extra) < ExtraVanity+ExtraSeal {
return errMissingSignature
}
// 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 {
return errExtraSigners
}
@ -292,14 +297,14 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
if 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")
}
// Verify the non-existence of withdrawalsHash.
if header.WithdrawalsHash != nil {
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")
}
// 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 {
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.
if header.BaseFee != nil {
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() {
copy(signers[i*common.AddressLength:], signer[:])
}
extraSuffix := len(header.Extra) - extraSeal
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) {
extraSuffix := len(header.Extra) - ExtraSeal
if !bytes.Equal(header.Extra[ExtraVanity:extraSuffix], signers) {
return errMismatchingCheckpointSigners
}
}
@ -404,9 +409,9 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash
if checkpoint != nil {
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++ {
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)
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)
// Ensure the extra data has all its components
if len(header.Extra) < extraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
if len(header.Extra) < ExtraVanity {
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 {
for _, signer := range snap.signers() {
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
header.MixDigest = common.Hash{}
@ -587,7 +592,8 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
c.Finalize(chain, header, state, body)
// 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.
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
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package clique
package clique_test
import (
"math/big"
"testing"
"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/rawdb"
"github.com/ethereum/go-ethereum/core/types"
@ -40,18 +41,19 @@ func TestReimportMirroredState(t *testing.T) {
db = rawdb.NewMemoryDatabase()
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
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)
)
genspec := &core.Genesis{
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{
addr: {Balance: big.NewInt(10000000000000000)},
},
BaseFee: big.NewInt(params.InitialBaseFee),
}
copy(genspec.ExtraData[extraVanity:], addr[:])
copy(genspec.ExtraData[clique.ExtraVanity:], addr[:])
// Generate a batch of blocks, each properly signed
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) {
// 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.
block.SetDifficulty(diffInTurn)
block.SetDifficulty(big.NewInt(clique.DiffInTurn))
// 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.
@ -80,7 +82,7 @@ func TestReimportMirroredState(t *testing.T) {
header.Extra = make([]byte, extraVanity+extraSeal)
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)
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/ethdb"
"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
@ -52,8 +51,8 @@ type sigLRU = lru.Cache[common.Hash, common.Address]
// Snapshot is the state of the authorization voting at a given point in time.
type Snapshot struct {
config *params.CliqueConfig // Consensus engine parameters to fine tune behavior
sigcache *sigLRU // Cache of recent block signatures to speed up ecrecover
config *Config // Consensus engine parameters to fine tune behavior
sigcache *sigLRU // Cache of recent block signatures to speed up ecrecover
Number uint64 `json:"number"` // Block number where the snapshot was created
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
@ -66,7 +65,7 @@ type Snapshot struct {
// 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
// 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{
config: config,
sigcache: sigcache,
@ -83,7 +82,7 @@ func newSnapshot(config *params.CliqueConfig, sigcache *sigLRU, number uint64, h
}
// 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[:]...))
if err != nil {
return nil, err

View file

@ -14,7 +14,7 @@
// 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
package clique_test
import (
"bytes"