From 215787789bc502ae84c712fc96cc0f7488fe8289 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 6 Aug 2025 23:42:57 +0200 Subject: [PATCH] consensus/clique: partial port to config2 --- consensus/clique/clique.go | 64 +++++++++++++++++-------------- consensus/clique/clique_test.go | 14 ++++--- consensus/clique/config.go | 31 +++++++++++++++ consensus/clique/snapshot.go | 9 ++--- consensus/clique/snapshot_test.go | 2 +- 5 files changed, 79 insertions(+), 41 deletions(-) create mode 100644 consensus/clique/config.go diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index b593d2117d..3fd4c3c634 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -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 ", 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 diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index afcab1d1f7..2bb57bb870 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -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 . -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) } diff --git a/consensus/clique/config.go b/consensus/clique/config.go new file mode 100644 index 0000000000..6bbbe2bb3d --- /dev/null +++ b/consensus/clique/config.go @@ -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 . + +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 +} diff --git a/consensus/clique/snapshot.go b/consensus/clique/snapshot.go index d0b15e9489..9029cbde64 100644 --- a/consensus/clique/snapshot.go +++ b/consensus/clique/snapshot.go @@ -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 diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index ac2355c730..aaecb1d98c 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -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 . -package clique +package clique_test import ( "bytes"