mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
1059 lines
36 KiB
Go
1059 lines
36 KiB
Go
package engine_v1
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
|
"github.com/XinFinOrg/XDPoSChain/common"
|
|
"github.com/XinFinOrg/XDPoSChain/common/lru"
|
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
|
"github.com/XinFinOrg/XDPoSChain/consensus/clique"
|
|
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
|
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
|
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
|
"github.com/XinFinOrg/XDPoSChain/log"
|
|
"github.com/XinFinOrg/XDPoSChain/params"
|
|
"github.com/XinFinOrg/XDPoSChain/trie"
|
|
)
|
|
|
|
const (
|
|
// timeout waiting for M1
|
|
minePeriod = 10
|
|
// timeout for checkpoint.
|
|
minePeriodCheckpoint = 20
|
|
)
|
|
|
|
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
|
|
// Ethereum testnet following the Ropsten attacks.
|
|
type XDPoS_v1 struct {
|
|
chainConfig *params.ChainConfig // Chain & network configuration
|
|
|
|
config *params.XDPoSConfig // Consensus engine configuration parameters
|
|
db ethdb.Database // Database to store and retrieve snapshot checkpoints
|
|
|
|
recents *lru.Cache[common.Hash, *SnapshotV1] // Snapshots for recent block to speed up reorgs
|
|
signatures *utils.SigLRU // Signatures of recent blocks to speed up mining
|
|
validatorSignatures *utils.SigLRU // Signatures of recent blocks to speed up mining
|
|
verifiedHeaders *lru.Cache[common.Hash, struct{}]
|
|
proposals map[common.Address]bool // Current list of proposals we are pushing
|
|
|
|
signer common.Address // Ethereum address of the signing key
|
|
signFn clique.SignerFn // Signer function to authorize hashes with
|
|
lock sync.RWMutex // Protects the signer fields
|
|
|
|
HookReward func(chain consensus.ChainReader, state vm.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error)
|
|
HookPenalty func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error)
|
|
HookPenaltyTIPSigning func(chain consensus.ChainReader, header *types.Header, candidate []common.Address) ([]common.Address, error)
|
|
HookValidator func(header *types.Header, signers []common.Address) ([]byte, error)
|
|
HookVerifyMNs func(header *types.Header, signers []common.Address) error
|
|
|
|
HookGetSignersFromContract func(blockHash common.Hash) ([]common.Address, error)
|
|
}
|
|
|
|
/*
|
|
V1 Block
|
|
|
|
SignerFn is a signer callback function to request a hash to be signed by a
|
|
backing account.
|
|
type SignerFn func(accounts.Account, []byte) ([]byte, error)
|
|
|
|
sigHash returns the hash which is used as input for the delegated-proof-of-stake
|
|
signing. It is the hash of the entire header apart from the 65 byte signature
|
|
contained at the end of the extra data.
|
|
|
|
Note, the method requires the extra data to be at least 65 bytes, otherwise it
|
|
panics. This is done to avoid accidentally using both forms (signature present
|
|
or not), which could be abused to produce different hashes for the same header.
|
|
*/
|
|
func (x *XDPoS_v1) SigHash(header *types.Header) (hash common.Hash) {
|
|
return sigHash(header)
|
|
}
|
|
|
|
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
|
|
// signers set to the ones provided by the user.
|
|
func New(chainConfig *params.ChainConfig, db ethdb.Database) *XDPoS_v1 {
|
|
config := chainConfig.XDPoS
|
|
// Set any missing consensus parameters to their defaults
|
|
conf := *config
|
|
if conf.Epoch == 0 {
|
|
conf.Epoch = utils.EpochLength
|
|
}
|
|
|
|
return &XDPoS_v1{
|
|
chainConfig: chainConfig,
|
|
|
|
config: &conf,
|
|
db: db,
|
|
|
|
recents: lru.NewCache[common.Hash, *SnapshotV1](utils.InMemorySnapshots),
|
|
signatures: lru.NewCache[common.Hash, common.Address](utils.InMemorySnapshots),
|
|
verifiedHeaders: lru.NewCache[common.Hash, struct{}](utils.InMemorySnapshots),
|
|
validatorSignatures: lru.NewCache[common.Hash, common.Address](utils.InMemorySnapshots),
|
|
proposals: make(map[common.Address]bool),
|
|
}
|
|
}
|
|
|
|
// Author implements consensus.Engine, returning the Ethereum address recovered
|
|
// from the signature in the header's extra-data section.
|
|
func (x *XDPoS_v1) Author(header *types.Header) (common.Address, error) {
|
|
return ecrecover(header, x.signatures)
|
|
}
|
|
|
|
// VerifyHeader checks whether a header conforms to the consensus rules.
|
|
func (x *XDPoS_v1) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
|
|
return x.verifyHeaderWithCache(chain, header, nil, fullVerify)
|
|
}
|
|
|
|
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
|
|
// method returns a quit channel to abort the operations and a results channel to
|
|
// retrieve the async verifications (the order is that of the input slice).
|
|
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool, abort <-chan struct{}, results chan<- error) {
|
|
go func() {
|
|
for i, header := range headers {
|
|
err := x.verifyHeaderWithCache(chain, header, headers[:i], fullVerifies[i])
|
|
|
|
select {
|
|
case <-abort:
|
|
return
|
|
case results <- err:
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (x *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
|
_, ok := x.verifiedHeaders.Get(header.Hash())
|
|
if ok {
|
|
return nil
|
|
}
|
|
err := x.verifyHeader(chain, header, parents, fullVerify)
|
|
if err == nil {
|
|
x.verifiedHeaders.Add(header.Hash(), struct{}{})
|
|
}
|
|
return err
|
|
}
|
|
|
|
// 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 (x *XDPoS_v1) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
|
// If we're running a engine faking, accept any block as valid
|
|
if x.config.SkipV1Validation {
|
|
return nil
|
|
}
|
|
if common.IsTestnet {
|
|
fullVerify = false
|
|
}
|
|
if header.Number == nil {
|
|
return utils.ErrUnknownBlock
|
|
}
|
|
number := header.Number.Uint64()
|
|
if fullVerify {
|
|
if header.Number.Uint64() > x.config.Epoch && len(header.Validator) == 0 {
|
|
return consensus.ErrNoValidatorSignature
|
|
}
|
|
// Don't waste time checking blocks from the future
|
|
if header.Time > uint64(time.Now().Unix()) {
|
|
return consensus.ErrFutureBlock
|
|
}
|
|
}
|
|
// Checkpoint blocks need to enforce zero beneficiary
|
|
checkpoint := (number % x.config.Epoch) == 0
|
|
if checkpoint && header.Coinbase != (common.Address{}) {
|
|
return utils.ErrInvalidCheckpointBeneficiary
|
|
}
|
|
|
|
// Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
|
|
if !bytes.Equal(header.Nonce[:], utils.NonceAuthVote) && !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
|
|
return utils.ErrInvalidVote
|
|
}
|
|
if checkpoint && !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
|
|
return utils.ErrInvalidCheckpointVote
|
|
}
|
|
// Check that the extra-data contains both the vanity and signature
|
|
if len(header.Extra) < utils.ExtraVanity {
|
|
return utils.ErrMissingVanity
|
|
}
|
|
if len(header.Extra) < utils.ExtraVanity+utils.ExtraSeal {
|
|
return utils.ErrMissingSignature
|
|
}
|
|
// Ensure that the extra-data contains a signer list on checkpoint, but none otherwise
|
|
signersBytes := len(header.Extra) - utils.ExtraVanity - utils.ExtraSeal
|
|
if !checkpoint && signersBytes != 0 {
|
|
return utils.ErrExtraSigners
|
|
}
|
|
if checkpoint && signersBytes%common.AddressLength != 0 {
|
|
return utils.ErrInvalidCheckpointSigners
|
|
}
|
|
// Ensure that the mix digest is zero as we don't have fork protection currently
|
|
if header.MixDigest != (common.Hash{}) {
|
|
return utils.ErrInvalidMixDigest
|
|
}
|
|
// Ensure that the block doesn't contain any uncles which are meaningless in XDPoS_v1
|
|
if header.UncleHash != utils.UncleHash {
|
|
return utils.ErrInvalidUncleHash
|
|
}
|
|
// All basic checks passed, verify cascading fields
|
|
return x.verifyCascadingFields(chain, header, parents, fullVerify)
|
|
}
|
|
|
|
// 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 (x *XDPoS_v1) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
|
// The genesis block is the always valid dead-end
|
|
number := header.Number.Uint64()
|
|
if number == 0 {
|
|
return nil
|
|
}
|
|
// Ensure that the block's timestamp isn't too close to it's parent
|
|
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+x.config.Period > header.Time {
|
|
return utils.ErrInvalidTimestamp
|
|
}
|
|
// Verify the header's EIP-1559 attributes.
|
|
if err := eip1559.VerifyEip1559Header(chain.Config(), header); err != nil {
|
|
return err
|
|
}
|
|
|
|
if number%x.config.Epoch != 0 {
|
|
return x.verifySeal(chain, header, parents, fullVerify)
|
|
}
|
|
|
|
/*
|
|
BUG: snapshot returns wrong signers sometimes
|
|
when it happens we get the signers list by requesting smart contract
|
|
*/
|
|
// Retrieve the snapshot needed to verify this header and cache it
|
|
snap, err := x.snapshot(chain, number-1, header.ParentHash, parents, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signers := snap.GetSigners()
|
|
err = x.checkSignersOnCheckpoint(chain, header, signers)
|
|
if err == nil {
|
|
return x.verifySeal(chain, header, parents, fullVerify)
|
|
}
|
|
|
|
signers, err = x.getSignersFromContract(chain, header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = x.checkSignersOnCheckpoint(chain, header, signers)
|
|
if err == nil {
|
|
return x.verifySeal(chain, header, parents, fullVerify)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (x *XDPoS_v1) checkSignersOnCheckpoint(chain consensus.ChainReader, header *types.Header, signers []common.Address) error {
|
|
number := header.Number.Uint64()
|
|
// ignore signerCheck at checkpoint block.
|
|
if common.IsIgnoreSignerCheckBlock(number) {
|
|
return nil
|
|
}
|
|
penPenalties := []common.Address{}
|
|
if x.HookPenalty != nil || x.HookPenaltyTIPSigning != nil {
|
|
var err error
|
|
if chain.Config().IsTIPSigning(header.Number) {
|
|
penPenalties, err = x.HookPenaltyTIPSigning(chain, header, signers)
|
|
} else {
|
|
penPenalties, err = x.HookPenalty(chain, number)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, address := range penPenalties {
|
|
log.Debug("Penalty Info", "address", address, "number", number)
|
|
}
|
|
bytePenalties := common.ExtractAddressToBytes(penPenalties)
|
|
if !bytes.Equal(header.Penalties, bytePenalties) {
|
|
return utils.ErrInvalidCheckpointPenalties
|
|
}
|
|
}
|
|
signers = common.RemoveItemFromArray(signers, penPenalties)
|
|
for i := 1; i <= common.LimitPenaltyEpoch; i++ {
|
|
if number > uint64(i)*x.config.Epoch {
|
|
signers = removePenaltiesFromBlock(chain, signers, number-uint64(i)*x.config.Epoch)
|
|
}
|
|
}
|
|
extraSuffix := len(header.Extra) - utils.ExtraSeal
|
|
masternodesFromCheckpointHeader := common.ExtractAddressFromBytes(header.Extra[utils.ExtraVanity:extraSuffix])
|
|
validSigners := utils.CompareSignersLists(masternodesFromCheckpointHeader, signers)
|
|
|
|
if !validSigners {
|
|
log.Error("Masternodes lists are different in checkpoint header and snapshot", "number", number)
|
|
for i, v := range masternodesFromCheckpointHeader {
|
|
log.Error("masternodes_from_checkpoint_header", "i", i, "addr", v.Hex())
|
|
}
|
|
for i, v := range signers {
|
|
log.Error("masternodes_in_snapshot", "i", i, "addr", v.Hex())
|
|
}
|
|
for i, v := range penPenalties {
|
|
log.Error("penPenalties", "i", i, "addr", v.Hex())
|
|
}
|
|
return utils.ErrInvalidCheckpointSigners
|
|
}
|
|
if x.HookVerifyMNs != nil {
|
|
err := x.HookVerifyMNs(header, signers)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *XDPoS_v1) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool {
|
|
snap, err := x.GetSnapshot(chain, header)
|
|
if err != nil {
|
|
log.Error("[IsAuthorisedAddress] Can't get snapshot with at ", "number", header.Number, "hash", header.Hash().Hex(), "err", err)
|
|
return false
|
|
}
|
|
if _, ok := snap.Signers[address]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (x *XDPoS_v1) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV1, error) {
|
|
number := header.Number.Uint64()
|
|
log.Trace("get snapshot", "number", number, "hash", header.Hash())
|
|
snap, err := x.snapshot(chain, number, header.Hash(), nil, header)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return snap, nil
|
|
}
|
|
|
|
func (x *XDPoS_v1) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) {
|
|
snap, err := x.GetSnapshot(chain, header)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return snap.GetSigners(), nil
|
|
}
|
|
|
|
func (x *XDPoS_v1) StoreSnapshot(snap *SnapshotV1) error {
|
|
return snap.store(x.db)
|
|
}
|
|
|
|
func (x *XDPoS_v1) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
|
n := header.Number.Uint64()
|
|
e := x.config.Epoch
|
|
switch {
|
|
case n%e == 0:
|
|
return x.GetMasternodesFromCheckpointHeader(header)
|
|
case n%e != 0:
|
|
h := chain.GetHeaderByNumber(n - (n % e))
|
|
if h == nil {
|
|
log.Warn("[GetMasternodes v1] epoch switch block header nil", "BlockNum", n)
|
|
}
|
|
return x.GetMasternodesFromCheckpointHeader(h)
|
|
default:
|
|
return []common.Address{}
|
|
}
|
|
}
|
|
|
|
func (x *XDPoS_v1) GetCurrentEpochSwitchBlock(blockNumber *big.Int) (uint64, uint64, error) {
|
|
currentBlockNum := blockNumber.Uint64()
|
|
currentCheckpointNumber := currentBlockNum - currentBlockNum%x.config.Epoch
|
|
epochNumber := currentBlockNum / x.config.Epoch
|
|
return currentCheckpointNumber, epochNumber, nil
|
|
}
|
|
|
|
func (x *XDPoS_v1) GetPeriod() uint64 { return x.config.Period }
|
|
|
|
func (x *XDPoS_v1) whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error) {
|
|
if header.Number.Uint64() == 0 {
|
|
return common.Address{}, errors.New("don't take block 0")
|
|
}
|
|
m, err := ecrecover(header, snap.sigcache)
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
return m, nil
|
|
}
|
|
func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
|
|
length, preIndex, curIndex, ok, err := x.yourTurn(chain, parent, signer)
|
|
|
|
if err != nil {
|
|
log.Warn("Failed when trying to commit new work", "err", err)
|
|
return false, err
|
|
}
|
|
if !ok {
|
|
// in case some nodes are down
|
|
if preIndex == -1 {
|
|
// first block
|
|
return false, nil
|
|
}
|
|
if curIndex == -1 {
|
|
// you're not allowed to create this block
|
|
return false, nil
|
|
}
|
|
h := utils.Hop(length, preIndex, curIndex)
|
|
gap := minePeriod * int64(h)
|
|
// Check nearest checkpoint block in hop range.
|
|
nearest := x.config.Epoch - (parent.Number.Uint64() % x.config.Epoch)
|
|
if uint64(h) >= nearest {
|
|
gap = minePeriodCheckpoint * int64(h)
|
|
}
|
|
log.Info("Distance from the parent block", "seconds", gap, "hops", h)
|
|
waitedTime := time.Now().Unix() - int64(parent.Time)
|
|
if gap > waitedTime {
|
|
return false, nil
|
|
}
|
|
log.Info("Wait enough. It's my turn", "waited seconds", waitedTime)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (x *XDPoS_v1) yourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
|
masternodes := x.GetMasternodes(chain, parent)
|
|
snap, err := x.GetSnapshot(chain, parent)
|
|
if err != nil {
|
|
log.Warn("Failed when trying to commit new work", "err", err)
|
|
return 0, -1, -1, false, err
|
|
}
|
|
if len(masternodes) == 0 {
|
|
return 0, -1, -1, false, errors.New("masternodes not found")
|
|
}
|
|
pre := common.Address{}
|
|
// masternode[0] has chance to create block 1
|
|
preIndex := -1
|
|
if parent.Number.Uint64() != 0 {
|
|
pre, err = x.whoIsCreator(snap, parent)
|
|
if err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
preIndex = utils.Position(masternodes, pre)
|
|
}
|
|
curIndex := utils.Position(masternodes, signer)
|
|
if signer == x.signer {
|
|
log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex)
|
|
}
|
|
for i, s := range masternodes {
|
|
log.Debug("Masternode:", "index", i, "address", s)
|
|
}
|
|
if (preIndex+1)%len(masternodes) == curIndex {
|
|
return len(masternodes), preIndex, curIndex, true, nil
|
|
}
|
|
return len(masternodes), preIndex, curIndex, false, nil
|
|
}
|
|
|
|
// snapshot retrieves the authorization snapshot at a given point in time.
|
|
func (x *XDPoS_v1) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header, selfHeader *types.Header) (*SnapshotV1, error) {
|
|
// Search for a SnapshotV1 in memory or on disk for checkpoints
|
|
var (
|
|
headers []*types.Header
|
|
snap *SnapshotV1
|
|
)
|
|
for {
|
|
// If an in-memory SnapshotV1 was found, use that
|
|
if s, ok := x.recents.Get(hash); ok && s != nil {
|
|
snap = s
|
|
break
|
|
}
|
|
// If an on-disk checkpoint snapshot can be found, use that
|
|
// checkpoint snapshot = checkpoint - gap
|
|
if (number+x.config.Gap)%x.config.Epoch == 0 {
|
|
if s, err := loadSnapshot(x.config, x.signatures, x.db, hash); err == nil {
|
|
log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash)
|
|
snap = s
|
|
break
|
|
}
|
|
}
|
|
// If we're at block zero, make a snapshot
|
|
if number == 0 {
|
|
genesis := chain.GetHeaderByNumber(0)
|
|
if err := x.VerifyHeader(chain, genesis, true); err != nil {
|
|
return nil, err
|
|
}
|
|
signers := make([]common.Address, (len(genesis.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
|
for i := 0; i < len(signers); i++ {
|
|
copy(signers[i][:], genesis.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
|
}
|
|
snap = newSnapshot(x.config, x.signatures, 0, genesis.Hash(), signers)
|
|
if err := snap.store(x.db); err != nil {
|
|
return nil, err
|
|
}
|
|
log.Trace("Stored genesis voting snapshot to disk")
|
|
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 if selfHeader != nil && selfHeader.Hash() == hash {
|
|
// it prevents db doesn't have current block info, can be removed by refactor blockchain.go reorg function call.
|
|
header = selfHeader
|
|
} 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(headers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
x.recents.Add(snap.Hash, snap)
|
|
|
|
// If we've generated a new checkpoint snapshot, save to disk
|
|
if (snap.Number+x.config.Gap)%x.config.Epoch == 0 {
|
|
if err = snap.store(x.db); err != nil {
|
|
return nil, err
|
|
}
|
|
log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
|
|
}
|
|
return snap, err
|
|
}
|
|
|
|
// VerifyUncles implements consensus.Engine, always returning an error for any
|
|
// uncles as this consensus mechanism doesn't permit uncles.
|
|
func (x *XDPoS_v1) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
|
if len(block.Uncles()) > 0 {
|
|
return errors.New("uncles not allowed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VerifySeal implements consensus.Engine, checking whether the signature contained
|
|
// in the header satisfies the consensus protocol requirements.
|
|
func (x *XDPoS_v1) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
|
return x.verifySeal(chain, header, nil, true)
|
|
}
|
|
|
|
// 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.
|
|
// verifySeal also checks the pair of creator-validator set in the header satisfies
|
|
// the double validation.
|
|
func (x *XDPoS_v1) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
|
// Verifying the genesis block is not supported
|
|
number := header.Number.Uint64()
|
|
if number == 0 {
|
|
return utils.ErrUnknownBlock
|
|
}
|
|
// Retrieve the snapshot needed to verify this header and cache it
|
|
snap, err := x.snapshot(chain, number-1, header.ParentHash, parents, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Resolve the authorization key and check against signers
|
|
creator, err := ecrecover(header, x.signatures)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var parent *types.Header
|
|
if len(parents) > 0 {
|
|
parent = parents[len(parents)-1]
|
|
} else {
|
|
parent = chain.GetHeader(header.ParentHash, number-1)
|
|
}
|
|
difficulty := x.calcDifficulty(chain, parent, creator)
|
|
log.Debug("verify seal block", "number", header.Number, "hash", header.Hash(), "block difficulty", header.Difficulty, "calc difficulty", difficulty, "creator", creator)
|
|
// Ensure that the block's difficulty is meaningful (may not be correct at this point)
|
|
if number > 0 {
|
|
if header.Difficulty.Int64() != difficulty.Int64() {
|
|
return utils.ErrInvalidDifficulty
|
|
}
|
|
}
|
|
masternodes := x.GetMasternodes(chain, header)
|
|
mstring := []string{}
|
|
for _, m := range masternodes {
|
|
mstring = append(mstring, m.String())
|
|
}
|
|
nstring := []string{}
|
|
for _, n := range snap.GetSigners() {
|
|
nstring = append(nstring, n.String())
|
|
}
|
|
if _, ok := snap.Signers[creator]; !ok {
|
|
valid := false
|
|
for _, m := range masternodes {
|
|
if m == creator {
|
|
valid = true
|
|
break
|
|
}
|
|
}
|
|
if !valid {
|
|
log.Debug("Unauthorized creator found", "block number", number, "creator", creator, "masternodes", mstring, "snapshot from parent block", nstring)
|
|
return utils.ErrUnauthorized
|
|
}
|
|
}
|
|
if len(masternodes) > 1 {
|
|
for seen, recent := range snap.Recents {
|
|
if recent == creator {
|
|
// Signer is among recents, only fail if the current block doesn't shift it out
|
|
// There is only case that we don't allow signer to create two continuous blocks.
|
|
if limit := uint64(2); seen > number-limit {
|
|
// Only take into account the non-epoch blocks
|
|
if number%x.config.Epoch != 0 {
|
|
return utils.ErrUnauthorized
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// header must contain validator info following double validation design
|
|
// start checking from epoch 2nd.
|
|
if header.Number.Uint64() > x.config.Epoch && fullVerify {
|
|
validator, err := x.RecoverValidator(header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// verify validator
|
|
assignedValidator, err := x.GetValidator(creator, chain, header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if validator != assignedValidator {
|
|
log.Debug("Bad block detected. Header contains wrong pair of creator-validator", "creator", creator, "assigned validator", assignedValidator, "wrong validator", validator)
|
|
return utils.ErrFailedDoubleValidation
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (x *XDPoS_v1) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) {
|
|
epoch := x.config.Epoch
|
|
no := header.Number.Uint64()
|
|
cpNo := no
|
|
if no%epoch != 0 {
|
|
cpNo = no - (no % epoch)
|
|
}
|
|
if cpNo == 0 {
|
|
return common.Address{}, nil
|
|
}
|
|
cpHeader := chain.GetHeaderByNumber(cpNo)
|
|
if cpHeader == nil {
|
|
if no%epoch == 0 {
|
|
cpHeader = header
|
|
} else {
|
|
return common.Address{}, errors.New("couldn't find checkpoint header")
|
|
}
|
|
}
|
|
m, err := getM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
return m[creator], nil
|
|
}
|
|
|
|
// Prepare implements consensus.Engine, preparing all the consensus fields of the
|
|
// header for running the transactions on top.
|
|
func (x *XDPoS_v1) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
|
// If the block isn't a checkpoint, cast a random vote (good enough for now)
|
|
header.Coinbase = common.Address{}
|
|
header.Nonce = types.BlockNonce{}
|
|
|
|
number := header.Number.Uint64()
|
|
// Assemble the voting snapshot to check which votes make sense
|
|
snap, err := x.snapshot(chain, number-1, header.ParentHash, nil, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
x.lock.RLock()
|
|
if number%x.config.Epoch != 0 {
|
|
// Gather all the proposals that make sense voting on
|
|
addresses := make([]common.Address, 0, len(x.proposals))
|
|
for address, authorize := range x.proposals {
|
|
if snap.validVote(address, authorize) {
|
|
addresses = append(addresses, address)
|
|
}
|
|
}
|
|
// If there's pending proposals, cast a vote on them
|
|
if len(addresses) > 0 {
|
|
header.Coinbase = addresses[rand.Intn(len(addresses))]
|
|
if x.proposals[header.Coinbase] {
|
|
copy(header.Nonce[:], utils.NonceAuthVote)
|
|
} else {
|
|
copy(header.Nonce[:], utils.NonceDropVote)
|
|
}
|
|
}
|
|
}
|
|
signer := x.signer
|
|
x.lock.RUnlock()
|
|
|
|
parent := chain.GetHeader(header.ParentHash, number-1)
|
|
if parent == nil {
|
|
return consensus.ErrUnknownAncestor
|
|
}
|
|
// Set the correct difficulty
|
|
header.Difficulty = x.calcDifficulty(chain, parent, signer)
|
|
log.Debug("CalcDifficulty ", "number", header.Number, "difficulty", header.Difficulty)
|
|
// Ensure the extra data has all it's components
|
|
if len(header.Extra) < utils.ExtraVanity {
|
|
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, utils.ExtraVanity-len(header.Extra))...)
|
|
}
|
|
header.Extra = header.Extra[:utils.ExtraVanity]
|
|
masternodes := snap.GetSigners()
|
|
if number >= x.config.Epoch && number%x.config.Epoch == 0 {
|
|
if x.HookPenalty != nil || x.HookPenaltyTIPSigning != nil {
|
|
var penMasternodes []common.Address
|
|
var err error
|
|
if chain.Config().IsTIPSigning(header.Number) {
|
|
penMasternodes, err = x.HookPenaltyTIPSigning(chain, header, masternodes)
|
|
} else {
|
|
penMasternodes, err = x.HookPenalty(chain, number)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(penMasternodes) > 0 {
|
|
// penalize bad masternode(s)
|
|
masternodes = common.RemoveItemFromArray(masternodes, penMasternodes)
|
|
for _, address := range penMasternodes {
|
|
log.Debug("Penalty status", "address", address, "number", number)
|
|
}
|
|
header.Penalties = common.ExtractAddressToBytes(penMasternodes)
|
|
}
|
|
}
|
|
// Prevent penalized masternode(s) within 4 recent epochs
|
|
for i := 1; i <= common.LimitPenaltyEpoch; i++ {
|
|
if number > uint64(i)*x.config.Epoch {
|
|
masternodes = removePenaltiesFromBlock(chain, masternodes, number-uint64(i)*x.config.Epoch)
|
|
}
|
|
}
|
|
for _, masternode := range masternodes {
|
|
header.Extra = append(header.Extra, masternode[:]...)
|
|
}
|
|
if x.HookValidator != nil {
|
|
validators, err := x.HookValidator(header, masternodes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header.Validators = validators
|
|
}
|
|
}
|
|
header.Extra = append(header.Extra, make([]byte, utils.ExtraSeal)...)
|
|
|
|
// Mix digest is reserved for now, set to empty
|
|
header.MixDigest = common.Hash{}
|
|
|
|
// Ensure the timestamp has the correct delay
|
|
|
|
header.Time = parent.Time + x.config.Period
|
|
if timeNow := uint64(time.Now().Unix()); header.Time < timeNow {
|
|
header.Time = timeNow
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Update masternodes into snapshot. In V1, truncating ms[:MaxMasternodes] is done in this function.
|
|
func (x *XDPoS_v1) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
|
|
number := header.Number.Uint64()
|
|
log.Trace("take snapshot", "number", number, "hash", header.Hash())
|
|
|
|
var maxMasternodes int
|
|
// check if block number is increase ms checkpoint
|
|
if x.chainConfig.IsTIPIncreaseMasternodes(header.Number) || (x.config.V2.SwitchBlock != nil && header.Number.Cmp(x.config.V2.SwitchBlock) == 1) {
|
|
// using new masterndoes
|
|
maxMasternodes = common.MaxMasternodesV2
|
|
} else {
|
|
// using old masterndoes
|
|
maxMasternodes = common.MaxMasternodes
|
|
}
|
|
if len(ms) > maxMasternodes {
|
|
ms = ms[:maxMasternodes]
|
|
}
|
|
// get snapshot
|
|
snap, err := x.snapshot(chain, number, header.Hash(), nil, header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newMasternodes := make(map[common.Address]struct{})
|
|
for _, m := range ms {
|
|
newMasternodes[m.Address] = struct{}{}
|
|
}
|
|
snap.Signers = newMasternodes
|
|
nm := []string{}
|
|
for _, n := range ms {
|
|
nm = append(nm, n.Address.String())
|
|
}
|
|
x.recents.Add(snap.Hash, snap)
|
|
log.Info("New set of masternodes has been updated to snapshot", "number", snap.Number, "hash", snap.Hash)
|
|
for i, v := range nm {
|
|
log.Info("masternodes", "i", i, "addr", v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
|
|
// rewards given, and returns the final block.
|
|
func (x *XDPoS_v1) Finalize(chain consensus.ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
|
// set block reward
|
|
number := header.Number.Uint64()
|
|
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint
|
|
|
|
// _ = c.CacheData(header, txs, receipts)
|
|
|
|
if x.HookReward != nil && number%rCheckpoint == 0 {
|
|
rewards, err := x.HookReward(chain, state, parentState, header)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(common.StoreRewardFolder) > 0 {
|
|
data, err := json.Marshal(rewards)
|
|
if err == nil {
|
|
err = os.WriteFile(filepath.Join(common.StoreRewardFolder, header.Number.String()+"."+header.Hash().Hex()), data, 0644)
|
|
}
|
|
if err != nil {
|
|
log.Error("Error when save reward info ", "number", header.Number, "hash", header.Hash().Hex(), "err", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// the state remains as is and uncles are dropped
|
|
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
|
header.UncleHash = types.CalcUncleHash(nil)
|
|
|
|
// Assemble and return the final block for sealing
|
|
return types.NewBlock(header, &types.Body{Transactions: txs}, receipts, trie.NewStackTrie(nil)), nil
|
|
}
|
|
|
|
// Authorize injects a private key into the consensus engine to mint new blocks
|
|
// with.
|
|
func (x *XDPoS_v1) Authorize(signer common.Address, signFn clique.SignerFn) {
|
|
x.lock.Lock()
|
|
defer x.lock.Unlock()
|
|
|
|
x.signer = signer
|
|
x.signFn = signFn
|
|
}
|
|
|
|
// Seal implements consensus.Engine, attempting to create a sealed block using
|
|
// the local signing credentials.
|
|
func (x *XDPoS_v1) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
|
|
header := block.Header()
|
|
|
|
// Sealing the genesis block is not supported
|
|
number := header.Number.Uint64()
|
|
if number == 0 {
|
|
return nil, utils.ErrUnknownBlock
|
|
}
|
|
// For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
|
|
// checkpoint blocks have no tx
|
|
if x.config.Period == 0 && len(block.Transactions()) == 0 && number%x.config.Epoch != 0 {
|
|
return nil, utils.ErrWaitTransactions
|
|
}
|
|
// Don't hold the signer fields for the entire sealing procedure
|
|
x.lock.RLock()
|
|
signer, signFn := x.signer, x.signFn
|
|
x.lock.RUnlock()
|
|
|
|
// Bail out if we're unauthorized to sign a block
|
|
snap, err := x.snapshot(chain, number-1, header.ParentHash, nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
masternodes := x.GetMasternodes(chain, header)
|
|
if _, authorized := snap.Signers[signer]; !authorized {
|
|
valid := false
|
|
for _, m := range masternodes {
|
|
if m == signer {
|
|
valid = true
|
|
break
|
|
}
|
|
}
|
|
if !valid {
|
|
return nil, utils.ErrUnauthorized
|
|
}
|
|
}
|
|
// If we're amongst the recent signers, wait for the next block
|
|
// only check recent signers if there are more than one signer.
|
|
if len(masternodes) > 1 {
|
|
for seen, recent := range snap.Recents {
|
|
if recent == signer {
|
|
// Signer is among recents, only wait if the current block doesn't shift it out
|
|
// There is only case that we don't allow signer to create two continuous blocks.
|
|
if limit := uint64(2); number < limit || seen > number-limit {
|
|
// Only take into account the non-epoch blocks
|
|
if number%x.config.Epoch != 0 {
|
|
log.Info("Signed recently, must wait for others ", "len(masternodes)", len(masternodes), "number", number, "limit", limit, "seen", seen, "recent", recent.String(), "snap.Recents", snap.Recents)
|
|
<-stop
|
|
return nil, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
select {
|
|
case <-stop:
|
|
return nil, nil
|
|
default:
|
|
}
|
|
// Sign all the things!
|
|
sighash, err := signFn(accounts.Account{Address: signer}, x.SigHash(header).Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
|
|
m2, err := x.GetValidator(signer, chain, header)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't get block validator: %v", err)
|
|
}
|
|
if m2 == signer {
|
|
header.Validator = sighash
|
|
}
|
|
return block.WithSeal(header), nil
|
|
}
|
|
|
|
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
|
// that a new block should have based on the previous blocks in the chain and the
|
|
// current signer.
|
|
func (x *XDPoS_v1) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
|
x.lock.RLock()
|
|
signer := x.signer
|
|
x.lock.RUnlock()
|
|
return x.calcDifficulty(chain, parent, signer)
|
|
}
|
|
|
|
func (x *XDPoS_v1) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int {
|
|
// If we're running a engine faking, skip calculation
|
|
if x.config.SkipV1Validation {
|
|
return big.NewInt(1)
|
|
}
|
|
length, preIndex, curIndex, _, err := x.yourTurn(chain, parent, signer)
|
|
if err != nil {
|
|
return big.NewInt(int64(length + curIndex - preIndex))
|
|
}
|
|
return big.NewInt(int64(length - utils.Hop(length, preIndex, curIndex)))
|
|
}
|
|
|
|
func (x *XDPoS_v1) RecoverSigner(header *types.Header) (common.Address, error) {
|
|
return ecrecover(header, x.signatures)
|
|
}
|
|
|
|
func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error) {
|
|
// If the signature's already cached, return that
|
|
hash := header.Hash()
|
|
if address, known := x.validatorSignatures.Get(hash); known {
|
|
return address, nil
|
|
}
|
|
// Retrieve the signature from the header.Validator
|
|
// len equals 65 bytes
|
|
if len(header.Validator) != utils.ExtraSeal {
|
|
return common.Address{}, consensus.ErrFailValidatorSignature
|
|
}
|
|
// Recover the public key and the Ethereum address
|
|
pubkey, err := crypto.Ecrecover(x.SigHash(header).Bytes(), header.Validator)
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
var signer common.Address
|
|
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
|
|
|
x.validatorSignatures.Add(hash, signer)
|
|
return signer, nil
|
|
}
|
|
|
|
// Get master nodes over extra data of checkpoint block.
|
|
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
|
if checkpointHeader == nil {
|
|
log.Warn("Checkpoint's header is empty", "Header", checkpointHeader)
|
|
return []common.Address{}
|
|
}
|
|
return decodeMasternodesFromHeaderExtra(checkpointHeader)
|
|
}
|
|
|
|
func (x *XDPoS_v1) GetDb() ethdb.Database {
|
|
return x.db
|
|
}
|
|
|
|
// Extract validators from byte array.
|
|
func removePenaltiesFromBlock(chain consensus.ChainReader, masternodes []common.Address, epochNumber uint64) []common.Address {
|
|
if epochNumber <= 0 {
|
|
return masternodes
|
|
}
|
|
header := chain.GetHeaderByNumber(epochNumber)
|
|
block := chain.GetBlock(header.Hash(), epochNumber)
|
|
penalties := block.Penalties()
|
|
if penalties != nil {
|
|
prevPenalties := common.ExtractAddressFromBytes(penalties)
|
|
masternodes = common.RemoveItemFromArray(masternodes, prevPenalties)
|
|
}
|
|
return masternodes
|
|
}
|
|
|
|
func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) {
|
|
startGapBlockHeader := checkpointHeader
|
|
number := checkpointHeader.Number.Uint64()
|
|
for step := uint64(1); step <= chain.Config().XDPoS.Gap; step++ {
|
|
startGapBlockHeader = chain.GetHeader(startGapBlockHeader.ParentHash, number-step)
|
|
}
|
|
signers, err := x.HookGetSignersFromContract(startGapBlockHeader.Hash())
|
|
if err != nil {
|
|
return []common.Address{}, fmt.Errorf("can't get signers from Smart Contract . Err: %v", err)
|
|
}
|
|
return signers, nil
|
|
}
|
|
|
|
func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS_v1 {
|
|
var fakeEngine *XDPoS_v1
|
|
// Set any missing consensus parameters to their defaults
|
|
conf := chainConfig.XDPoS
|
|
|
|
fakeEngine = &XDPoS_v1{
|
|
chainConfig: chainConfig,
|
|
|
|
config: conf,
|
|
db: db,
|
|
recents: lru.NewCache[common.Hash, *SnapshotV1](utils.InMemorySnapshots),
|
|
signatures: lru.NewCache[common.Hash, common.Address](utils.InMemorySnapshots),
|
|
verifiedHeaders: lru.NewCache[common.Hash, struct{}](utils.InMemorySnapshots),
|
|
validatorSignatures: lru.NewCache[common.Hash, common.Address](utils.InMemorySnapshots),
|
|
proposals: make(map[common.Address]bool),
|
|
}
|
|
return fakeEngine
|
|
}
|
|
|
|
// Epoch Switch is also known as checkpoint in v1
|
|
func (x *XDPoS_v1) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
|
epochNumber := header.Number.Uint64() / x.config.Epoch
|
|
blockNumInEpoch := header.Number.Uint64() % x.config.Epoch
|
|
return blockNumInEpoch == 0, epochNumber, nil
|
|
}
|