148 lines
5.6 KiB
Go
148 lines
5.6 KiB
Go
// Copyright 2021 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 core
|
|
|
|
import (
|
|
crand "crypto/rand"
|
|
"errors"
|
|
"math/big"
|
|
mrand "math/rand"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/math"
|
|
"github.com/ethereum/go-ethereum/consensus"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
// ChainReader defines a small collection of methods needed to access the local
|
|
// blockchain during header verification. It's implemented by both blockchain
|
|
// and lightchain.
|
|
type ChainReader interface {
|
|
// Config retrieves the header chain's chain configuration.
|
|
Config() *params.ChainConfig
|
|
|
|
// Engine retrieves the blockchain's consensus engine.
|
|
Engine() consensus.Engine
|
|
|
|
// GetJustifiedNumber returns the highest justified blockNumber on the branch including and before `header`
|
|
GetJustifiedNumber(header *types.Header) uint64
|
|
|
|
// GetTd returns the total difficulty of a local block.
|
|
GetTd(common.Hash, uint64) *big.Int
|
|
}
|
|
|
|
// ForkChoice is the fork chooser based on the highest total difficulty of the
|
|
// chain(the fork choice used in the eth1) and the external fork choice (the fork
|
|
// choice used in the eth2). This main goal of this ForkChoice is not only for
|
|
// offering fork choice during the eth1/2 merge phase, but also keep the compatibility
|
|
// for all other proof-of-work networks.
|
|
type ForkChoice struct {
|
|
chain ChainReader
|
|
rand *mrand.Rand
|
|
|
|
// preserve is a helper function used in td fork choice.
|
|
// Miners will prefer to choose the local mined block if the
|
|
// local td is equal to the extern one. It can be nil for light
|
|
// client
|
|
preserve func(header *types.Header) bool
|
|
}
|
|
|
|
func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header) bool) *ForkChoice {
|
|
// Seed a fast but crypto originating random generator
|
|
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
|
|
if err != nil {
|
|
log.Crit("Failed to initialize random seed", "err", err)
|
|
}
|
|
return &ForkChoice{
|
|
chain: chainReader,
|
|
rand: mrand.New(mrand.NewSource(seed.Int64())),
|
|
preserve: preserve,
|
|
}
|
|
}
|
|
|
|
// reorgNeeded returns whether the reorg should be applied
|
|
// based on the given external header and local canonical chain.
|
|
// In the td mode, the new head is chosen if the corresponding
|
|
// total difficulty is higher. In the extern mode, the trusted
|
|
// header is always selected as the head.
|
|
func (f *ForkChoice) ReorgNeeded(current *types.Header, extern *types.Header) (bool, error) {
|
|
var (
|
|
localTD = f.chain.GetTd(current.Hash(), current.Number.Uint64())
|
|
externTd = f.chain.GetTd(extern.Hash(), extern.Number.Uint64())
|
|
)
|
|
if localTD == nil || externTd == nil {
|
|
return false, errors.New("missing td")
|
|
}
|
|
// Accept the new header as the chain head if the transition
|
|
// is already triggered. We assume all the headers after the
|
|
// transition come from the trusted consensus layer.
|
|
if ttd := f.chain.Config().TerminalTotalDifficulty; ttd != nil && ttd.Cmp(externTd) <= 0 {
|
|
return true, nil
|
|
}
|
|
|
|
// If the total difficulty is higher than our known, add it to the canonical chain
|
|
if diff := externTd.Cmp(localTD); diff > 0 {
|
|
return true, nil
|
|
} else if diff < 0 {
|
|
return false, nil
|
|
}
|
|
// Local and external difficulty is identical.
|
|
// Second clause in the if statement reduces the vulnerability to selfish mining.
|
|
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
|
|
reorg := false
|
|
externNum, localNum := extern.Number.Uint64(), current.Number.Uint64()
|
|
if externNum < localNum {
|
|
reorg = true
|
|
} else if externNum == localNum {
|
|
var currentPreserve, externPreserve bool
|
|
if f.preserve != nil {
|
|
currentPreserve, externPreserve = f.preserve(current), f.preserve(extern)
|
|
}
|
|
reorg = !currentPreserve && (externPreserve ||
|
|
extern.Time < current.Time ||
|
|
extern.Time == current.Time && f.rand.Float64() < 0.5)
|
|
}
|
|
return reorg, nil
|
|
}
|
|
|
|
// ReorgNeededWithFastFinality compares justified block numbers firstly, backoff to compare tds when equal
|
|
func (f *ForkChoice) ReorgNeededWithFastFinality(current *types.Header, header *types.Header) (bool, error) {
|
|
_, ok := f.chain.Engine().(consensus.PoSA)
|
|
if !ok {
|
|
return f.ReorgNeeded(current, header)
|
|
}
|
|
|
|
justifiedNumber, curJustifiedNumber := uint64(0), uint64(0)
|
|
if f.chain.Config().IsPlato(header.Number) {
|
|
justifiedNumber = f.chain.GetJustifiedNumber(header)
|
|
}
|
|
if f.chain.Config().IsPlato(current.Number) {
|
|
curJustifiedNumber = f.chain.GetJustifiedNumber(current)
|
|
}
|
|
if justifiedNumber == curJustifiedNumber {
|
|
return f.ReorgNeeded(current, header)
|
|
}
|
|
|
|
if justifiedNumber > curJustifiedNumber && header.Number.Cmp(current.Number) <= 0 {
|
|
log.Info("Chain find higher justifiedNumber", "fromHeight", current.Number, "fromHash", current.Hash(), "fromMiner", current.Coinbase, "fromJustified", curJustifiedNumber,
|
|
"toHeight", header.Number, "toHash", header.Hash(), "toMiner", header.Coinbase, "toJustified", justifiedNumber)
|
|
}
|
|
return justifiedNumber > curJustifiedNumber, nil
|
|
}
|