mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 19:50:36 +00:00
280 lines
9.4 KiB
Go
280 lines
9.4 KiB
Go
package clique
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"time"
|
|
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/common/length"
|
|
"github.com/ledgerwatch/log/v3"
|
|
|
|
"github.com/ledgerwatch/erigon/consensus"
|
|
"github.com/ledgerwatch/erigon/consensus/misc"
|
|
"github.com/ledgerwatch/erigon/core/types"
|
|
"github.com/ledgerwatch/erigon/params"
|
|
)
|
|
|
|
// verifyHeader checks whether a header conforms to the consensus rules.The
|
|
// caller may optionally pass in a batch of parents (ascending order) to avoid
|
|
// looking those up from the database. This is useful for concurrently verifying
|
|
// a batch of new headers.
|
|
func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
|
|
if header.Number == nil {
|
|
return errUnknownBlock
|
|
}
|
|
number := header.Number.Uint64()
|
|
|
|
now := time.Now()
|
|
nowUnix := now.Unix()
|
|
|
|
// Don't waste time checking blocks from the future
|
|
if header.Time > uint64(nowUnix) {
|
|
return consensus.ErrFutureBlock
|
|
}
|
|
|
|
// Checkpoint blocks need to enforce zero beneficiary
|
|
checkpoint := (number % c.config.Epoch) == 0
|
|
if checkpoint && header.Coinbase != (libcommon.Address{}) {
|
|
return errInvalidCheckpointBeneficiary
|
|
}
|
|
|
|
// Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
|
|
if !bytes.Equal(header.Nonce[:], NonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) {
|
|
return errInvalidVote
|
|
}
|
|
|
|
if checkpoint && !bytes.Equal(header.Nonce[:], nonceDropVote) {
|
|
return errInvalidCheckpointVote
|
|
}
|
|
|
|
// Check that the extra-data contains both the vanity and signature
|
|
if len(header.Extra) < ExtraVanity {
|
|
return errMissingVanity
|
|
}
|
|
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
|
|
if !checkpoint && signersBytes != 0 {
|
|
return errExtraSigners
|
|
}
|
|
if checkpoint && signersBytes%length.Addr != 0 {
|
|
return errInvalidCheckpointSigners
|
|
}
|
|
// Ensure that the mix digest is zero as we don't have fork protection currently
|
|
if header.MixDigest != (libcommon.Hash{}) {
|
|
return errInvalidMixDigest
|
|
}
|
|
// Ensure that the block doesn't contain any uncles which are meaningless in PoA
|
|
if header.UncleHash != uncleHash {
|
|
return errInvalidUncleHash
|
|
}
|
|
// 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) {
|
|
return errInvalidDifficulty
|
|
}
|
|
}
|
|
|
|
if header.WithdrawalsHash != nil {
|
|
return consensus.ErrUnexpectedWithdrawals
|
|
}
|
|
|
|
// All basic checks passed, verify cascading fields
|
|
return c.verifyCascadingFields(chain, header, parents)
|
|
}
|
|
|
|
// verifyCascadingFields verifies all the header fields that are not standalone,
|
|
// rather depend on a batch of previous headers. The caller may optionally pass
|
|
// in a batch of parents (ascending order) to avoid looking those up from the
|
|
// database. This is useful for concurrently verifying a batch of new headers.
|
|
func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
|
|
// The genesis block is the always valid dead-end
|
|
number := header.Number.Uint64()
|
|
if number == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Retrieve the snapshot needed to verify this header and cache it
|
|
var parent *types.Header
|
|
if len(parents) > 0 {
|
|
parent = parents[len(parents)-1]
|
|
} else {
|
|
parent = chain.GetHeader(header.ParentHash, number-1)
|
|
}
|
|
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
|
|
return consensus.ErrUnknownAncestor
|
|
}
|
|
|
|
if parent.Time+c.config.Period > header.Time {
|
|
return errInvalidTimestamp
|
|
}
|
|
if !chain.Config().IsLondon(header.Number.Uint64()) {
|
|
// 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)
|
|
}
|
|
// Verify that the gas limit remains within allowed bounds
|
|
diff := int64(parent.GasLimit) - int64(header.GasLimit)
|
|
if diff < 0 {
|
|
diff *= -1
|
|
}
|
|
limit := parent.GasLimit / params.GasLimitBoundDivisor
|
|
|
|
if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
|
|
return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
|
|
}
|
|
} else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil {
|
|
// Verify the header's EIP-1559 attributes.
|
|
return err
|
|
}
|
|
|
|
// Retrieve the snapshot needed to verify this header and cache it
|
|
snap, err := c.Snapshot(chain, number-1, header.ParentHash, parents)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the block is a checkpoint block, verify the signer list
|
|
if number%c.config.Epoch == 0 {
|
|
signers := make([]byte, len(snap.Signers)*length.Addr)
|
|
for i, signer := range snap.GetSigners() {
|
|
copy(signers[i*length.Addr:], signer[:])
|
|
}
|
|
|
|
extraSuffix := len(header.Extra) - ExtraSeal
|
|
if !bytes.Equal(header.Extra[ExtraVanity:extraSuffix], signers) {
|
|
return errMismatchingCheckpointSigners
|
|
}
|
|
}
|
|
|
|
// All basic checks passed, verify the seal and return
|
|
return c.verifySeal(chain, header, snap)
|
|
}
|
|
|
|
func (c *Clique) Snapshot(chain consensus.ChainHeaderReader, number uint64, hash libcommon.Hash, parents []*types.Header) (*Snapshot, error) {
|
|
// Search for a snapshot in memory or on disk for checkpoints
|
|
var (
|
|
headers []*types.Header
|
|
snap *Snapshot
|
|
)
|
|
for snap == nil {
|
|
// If an in-memory snapshot was found, use that
|
|
if s, ok := c.recents.Get(hash); ok {
|
|
snap = s
|
|
break
|
|
}
|
|
// If an on-disk checkpoint snapshot can be found, use that
|
|
if number%c.snapshotConfig.CheckpointInterval == 0 {
|
|
if s, err := loadSnapshot(c.config, c.db, number, hash); err == nil {
|
|
log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash)
|
|
snap = s
|
|
break
|
|
}
|
|
}
|
|
// If we're at the genesis, snapshot the initial state. Alternatively if we're
|
|
// at a checkpoint block without a parent (light client CHT), or we have piled
|
|
// up more headers than allowed to be reorged (chain reinit from a freezer),
|
|
// consider the checkpoint trusted and snapshot it.
|
|
if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
|
|
checkpoint := chain.GetHeaderByNumber(number)
|
|
if checkpoint != nil {
|
|
hash := checkpoint.Hash()
|
|
|
|
signers := make([]libcommon.Address, (len(checkpoint.Extra)-ExtraVanity-ExtraSeal)/length.Addr)
|
|
for i := 0; i < len(signers); i++ {
|
|
copy(signers[i][:], checkpoint.Extra[ExtraVanity+i*length.Addr:])
|
|
}
|
|
snap = newSnapshot(c.config, number, hash, signers)
|
|
if err := snap.store(c.db); err != nil {
|
|
return nil, err
|
|
}
|
|
log.Info("[Clique] Stored checkpoint snapshot to disk", "number", number, "hash", hash)
|
|
break
|
|
}
|
|
}
|
|
// No snapshot for this header, gather the header and move backward
|
|
var header *types.Header
|
|
if len(parents) > 0 {
|
|
// If we have explicit parents, pick from there (enforced)
|
|
header = parents[len(parents)-1]
|
|
if header.Hash() != hash || header.Number.Uint64() != number {
|
|
return nil, consensus.ErrUnknownAncestor
|
|
}
|
|
parents = parents[:len(parents)-1]
|
|
} else {
|
|
// No explicit parents (or no more left), reach out to the database
|
|
header = chain.GetHeader(hash, number)
|
|
if header == nil {
|
|
return nil, consensus.ErrUnknownAncestor
|
|
}
|
|
}
|
|
headers = append(headers, header)
|
|
number, hash = number-1, header.ParentHash
|
|
}
|
|
// Previous snapshot found, apply any pending headers on top of it
|
|
for i := 0; i < len(headers)/2; i++ {
|
|
headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i]
|
|
}
|
|
snap, err := snap.apply(c.signatures, headers...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.recents.Add(snap.Hash, snap)
|
|
|
|
// If we've generated a new checkpoint snapshot, save to disk
|
|
if snap.Number%c.snapshotConfig.CheckpointInterval == 0 && len(headers) > 0 {
|
|
if err = snap.store(c.db); err != nil {
|
|
return nil, err
|
|
}
|
|
log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
|
|
}
|
|
return snap, err
|
|
}
|
|
|
|
// verifySeal checks whether the signature contained in the header satisfies the
|
|
// consensus protocol requirements. The method accepts an optional list of parent
|
|
// headers that aren't yet part of the local blockchain to generate the snapshots
|
|
// from.
|
|
func (c *Clique) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, snap *Snapshot) error {
|
|
// Verifying the genesis block is not supported
|
|
number := header.Number.Uint64()
|
|
if number == 0 {
|
|
return errUnknownBlock
|
|
}
|
|
|
|
// Resolve the authorization key and check against signers
|
|
signer, err := ecrecover(header, c.signatures)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, ok := snap.Signers[signer]; !ok {
|
|
return ErrUnauthorizedSigner
|
|
}
|
|
|
|
for seen, recent := range snap.Recents {
|
|
if recent == signer {
|
|
// Signer is among RecentsRLP, only fail if the current block doesn't shift it out
|
|
if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
|
|
return ErrRecentlySigned
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return errWrongDifficulty
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|