// Copyright (c) 2021 XDPoSChain // // This program 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. // // This program 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 this program. If not, see . // Package XDPoS is the adaptor for different consensus engine. package XDPoS import ( "errors" "fmt" "math/big" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common/lru" "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/clique" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/rpc" ) const ( ExtraFieldCheck = true SkipExtraFieldCheck = false newRoundChanSize = 1 ) func (x *XDPoS) SigHash(header *types.Header) (hash common.Hash) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.SignHash(header) default: // Default "v1" return x.EngineV1.SigHash(header) } } // XDPoS is the delegated-proof-of-stake consensus engine proposed to support the // Ethereum testnet following the Ropsten attacks. type XDPoS struct { config *params.XDPoSConfig // Consensus engine configuration parameters db ethdb.Database // Database to store and retrieve snapshot checkpoints // Transaction cache, only make sense for adaptor level signingTxsCache *lru.Cache[common.Hash, []*types.Transaction] // Share Channel MinePeriodCh chan int // Miner wait Period Channel NewRoundCh chan types.Round // Miner use this channel to trigger worker to commitNewWork // Trading and lending service GetXDCXService func() utils.TradingService GetLendingService func() utils.LendingService // The exact consensus engine with different versions EngineV1 *engine_v1.XDPoS_v1 EngineV2 *engine_v2.XDPoS_v2 } // Subscribe to consensus engines forensics events. Currently only exist for engine v2 func (x *XDPoS) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription { return x.EngineV2.ForensicsProcessor.SubscribeForensicsEvent(ch) } // 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 { log.Info("[New] initialise consensus engines") config := chainConfig.XDPoS // Set any missing consensus parameters to their defaults if config.Epoch == 0 { config.Epoch = utils.EpochLength } // For testing and testing project, default to mainnet config if config.V2 == nil { config.V2 = ¶ms.V2{ SwitchBlock: params.XDCMainnetChainConfig.XDPoS.V2.SwitchBlock, CurrentConfig: params.MainnetV2Configs[0], AllConfigs: params.MainnetV2Configs, } } if config.V2.SwitchBlock.Uint64()%config.Epoch != 0 { panic(fmt.Sprintf("v2 switch number is not epoch switch block %d, epoch %d", config.V2.SwitchBlock.Uint64(), config.Epoch)) } log.Info("xdc config loading", "v2 config", config.V2) minePeriodCh := make(chan int) newRoundCh := make(chan types.Round, newRoundChanSize) return &XDPoS{ config: config, db: db, MinePeriodCh: minePeriodCh, NewRoundCh: newRoundCh, signingTxsCache: lru.NewCache[common.Hash, []*types.Transaction](utils.BlockSignersCacheLimit), EngineV1: engine_v1.New(chainConfig, db), EngineV2: engine_v2.New(chainConfig, db, minePeriodCh, newRoundCh), } } // Stop stops the consensus engine: // - close chanel MinePeriodCh // - close chanel NewRoundCh func (x *XDPoS) Stop() { close(x.MinePeriodCh) close(x.NewRoundCh) } // NewFullFaker creates an ethash consensus engine with a full fake scheme that // accepts all blocks as valid, without checking any consensus rules whatsoever. func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS { var fakeEngine *XDPoS // Set any missing consensus parameters to their defaults conf := params.TestXDPoSMockChainConfig.XDPoS if chainConfig != nil { conf = chainConfig.XDPoS } minePeriodCh := make(chan int) newRoundCh := make(chan types.Round, newRoundChanSize) fakeEngine = &XDPoS{ config: conf, db: db, MinePeriodCh: minePeriodCh, NewRoundCh: newRoundCh, GetXDCXService: func() utils.TradingService { return nil }, GetLendingService: func() utils.LendingService { return nil }, signingTxsCache: lru.NewCache[common.Hash, []*types.Transaction](utils.BlockSignersCacheLimit), EngineV1: engine_v1.NewFaker(db, chainConfig), EngineV2: engine_v2.New(chainConfig, db, minePeriodCh, newRoundCh), } return fakeEngine } // Reset parameters after checkpoint due to config may change func (x *XDPoS) UpdateParams(header *types.Header) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: x.EngineV2.UpdateParams(header) return default: // Default "v1" return } } func (x *XDPoS) Initial(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.Initial(chain, header) default: // Default "v1" return nil } } /* Eth Consensus engine interface implementation */ // APIs implements consensus.Engine, returning the user facing RPC API to allow // controlling the signer voting. func (x *XDPoS) APIs(chain consensus.ChainReader) []rpc.API { return []rpc.API{{ Namespace: "XDPoS", Service: &API{chain: chain, XDPoS: x}, }} } // Author implements consensus.Engine, returning the Ethereum address recovered // from the signature in the header's extra-data section. func (x *XDPoS) Author(header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.Author(header) default: // Default "v1" return x.EngineV1.Author(header) } } // VerifyHeader checks whether a header conforms to the consensus rules. func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.VerifyHeader(chain, header, fullVerify) default: // Default "v1" return x.EngineV1.VerifyHeader(chain, header, 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) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) { abort := make(chan struct{}) results := make(chan error, len(headers)) // Split the headers list into v1 and v2 buckets var v1headers []*types.Header var v2headers []*types.Header v1fullVerifies := make([]bool, 0, len(headers)) v2fullVerifies := make([]bool, 0, len(headers)) for i, header := range headers { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: v2headers = append(v2headers, header) v2fullVerifies = append(v2fullVerifies, fullVerifies[i]) default: // Default "v1" v1headers = append(v1headers, header) v1fullVerifies = append(v1fullVerifies, fullVerifies[i]) } } if v1headers != nil { x.EngineV1.VerifyHeaders(chain, v1headers, v1fullVerifies, abort, results) } if v2headers != nil { x.EngineV2.VerifyHeaders(chain, v2headers, v2fullVerifies, abort, results) } return abort, results } // VerifyUncles implements consensus.Engine, always returning an error for any // uncles as this consensus mechanism doesn't permit uncles. func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { switch x.config.BlockConsensusVersion(block.Number()) { case params.ConsensusEngineVersion2: return x.EngineV2.VerifyUncles(chain, block) default: // Default "v1" return x.EngineV1.VerifyUncles(chain, block) } } // VerifySeal implements consensus.Engine, checking whether the signature contained // in the header satisfies the consensus protocol requirements. func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return nil default: // Default "v1" return x.EngineV1.VerifySeal(chain, header) } } // Prepare implements consensus.Engine, preparing all the consensus fields of the // header for running the transactions on top. func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.Prepare(chain, header) default: // Default "v1" return x.EngineV1.Prepare(chain, header) } } // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given, and returns the final block. func (x *XDPoS) 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) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts) default: // Default "v1" return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts) } } // Seal implements consensus.Engine, attempting to create a sealed block using // the local signing credentials. func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { switch x.config.BlockConsensusVersion(block.Number()) { case params.ConsensusEngineVersion2: return x.EngineV2.Seal(chain, block, stop) default: // Default "v1" return x.EngineV1.Seal(chain, block, stop) } } // 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) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { switch x.config.BlockConsensusVersion(parent.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.CalcDifficulty(chain, time, parent) default: // Default "v1" return x.EngineV1.CalcDifficulty(chain, time, parent) } } func (x *XDPoS) HandleProposedBlock(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.ProposedBlockHandler(chain, header) default: // Default "v1" return nil } } /* XDC specific methods */ // Authorize injects a private key into the consensus engine to mint new blocks // with. func (x *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) { // Authorize each consensus individually x.EngineV1.Authorize(signer, signFn) x.EngineV2.Authorize(signer, signFn) } func (x *XDPoS) GetPeriod() uint64 { return x.config.Period } func (x *XDPoS) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.IsAuthorisedAddress(chain, header, address) default: // Default "v1" return x.EngineV1.IsAuthorisedAddress(chain, header, address) } } func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.GetMasternodes(chain, header) default: // Default "v1" return x.EngineV1.GetMasternodes(chain, header) } } func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber uint64) []common.Address { blockHeader := chain.GetHeaderByNumber(blockNumber) if blockHeader == nil { log.Error("[GetMasternodesByNumber] Unable to find block", "Num", blockNumber) return []common.Address{} } switch x.config.BlockConsensusVersion(big.NewInt(int64(blockNumber))) { case params.ConsensusEngineVersion2: return x.EngineV2.GetMasternodes(chain, blockHeader) default: // Default "v1" return x.EngineV1.GetMasternodes(chain, blockHeader) } } func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) { switch x.config.BlockConsensusVersion(big.NewInt(parent.Number.Int64() + 1)) { case params.ConsensusEngineVersion2: return x.EngineV2.YourTurn(chain, parent, signer) default: // Default "v1" return x.EngineV1.YourTurn(chain, parent, signer) } } func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { default: // Default "v1", v2 does not need this function return x.EngineV1.GetValidator(creator, chain, header) } } func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error { // fmt.Println("UpdateMasternodes") switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.UpdateMasternodes(chain, header, ms) default: // Default "v1" return x.EngineV1.UpdateMasternodes(chain, header, ms) } } func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return common.Address{}, nil default: // Default "v1" return x.EngineV1.RecoverSigner(header) } } func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return common.Address{}, nil default: // Default "v1" return x.EngineV1.RecoverValidator(header) } } // Get master nodes over extra data of previous checkpoint block. func (x *XDPoS) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address { switch x.config.BlockConsensusVersion(checkpointHeader.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.GetMasternodesFromEpochSwitchHeader(checkpointHeader) default: // Default "v1" return x.EngineV1.GetMasternodesFromCheckpointHeader(checkpointHeader) } } // Check is epoch switch (checkpoint) block func (x *XDPoS) IsEpochSwitch(header *types.Header) (bool, uint64, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.IsEpochSwitch(header) default: // Default "v1" return x.EngineV1.IsEpochSwitch(header) } } func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNumber *big.Int) (uint64, uint64, error) { switch x.config.BlockConsensusVersion(blockNumber) { case params.ConsensusEngineVersion2: return x.EngineV2.GetCurrentEpochSwitchBlock(chain, blockNumber) default: // Default "v1" return x.EngineV1.GetCurrentEpochSwitchBlock(blockNumber) } } func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.CalculateMissingRounds(chain, header) default: // Default "v1" return nil, errors.New("not supported in the v1 consensus") } } // Same DB across all consensus engines func (x *XDPoS) GetDb() ethdb.Database { return x.db } func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: sp, err := x.EngineV2.GetSnapshot(chain, header) if err != nil { return nil, err } return &utils.PublicApiSnapshot{ Number: sp.Number, Hash: sp.Hash, Signers: sp.GetMappedCandidates(), }, err default: // Default "v1" sp, err := x.EngineV1.GetSnapshot(chain, header) if err != nil { return nil, err } // Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API return &utils.PublicApiSnapshot{ Number: sp.Number, Hash: sp.Hash, Signers: sp.Signers, Recents: sp.Recents, Votes: sp.Votes, Tally: sp.Tally, }, err } } func (x *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.GetSignersFromSnapshot(chain, header) default: // Default "v1" return x.EngineV1.GetAuthorisedSignersFromSnapshot(chain, header) } } func (x *XDPoS) FindParentBlockToAssign(chain consensus.ChainReader, currentBlock *types.Header) *types.Block { var parent *types.Block = nil if x.config.BlockConsensusVersion(currentBlock.Number) == params.ConsensusEngineVersion2 { parent = x.EngineV2.FindParentBlockToAssign(chain) } if parent == nil { parent = chain.GetBlock(currentBlock.Hash(), currentBlock.Number.Uint64()) } return parent } /** Caching */ // Cache signing transaction data into BlockSingers cache object func (x *XDPoS) CacheNoneTIPSigningTxs(header *types.Header, txs []*types.Transaction, receipts []*types.Receipt) []*types.Transaction { signTxs := []*types.Transaction{} for _, tx := range txs { if tx.IsSigningTransaction() { var b uint64 for _, r := range receipts { if r.TxHash == tx.Hash() { if len(r.PostState) > 0 { b = types.ReceiptStatusSuccessful } else { b = r.Status } break } } if b == types.ReceiptStatusFailed { continue } signTxs = append(signTxs, tx) } } log.Debug("Save tx signers to cache", "hash", header.Hash(), "number", header.Number, "len(txs)", len(signTxs)) x.signingTxsCache.Add(header.Hash(), signTxs) return signTxs } // Cache func (x *XDPoS) CacheSigningTxs(hash common.Hash, txs []*types.Transaction) []*types.Transaction { signTxs := []*types.Transaction{} for _, tx := range txs { if tx.IsSigningTransaction() { signTxs = append(signTxs, tx) } } log.Debug("Save tx signers to cache", "hash", hash, "len(txs)", len(signTxs)) x.signingTxsCache.Add(hash, signTxs) return signTxs } func (x *XDPoS) GetCachedSigningTxs(hash common.Hash) ([]*types.Transaction, bool) { return x.signingTxsCache.Get(hash) } func (x *XDPoS) GetEpochSwitchInfoBetween(chain consensus.ChainReader, begin, end *types.Header) ([]*types.EpochSwitchInfo, error) { beginBlockVersion := x.config.BlockConsensusVersion(begin.Number) endBlockVersion := x.config.BlockConsensusVersion(end.Number) if beginBlockVersion == params.ConsensusEngineVersion2 && endBlockVersion == params.ConsensusEngineVersion2 { return x.EngineV2.GetEpochSwitchInfoBetween(chain, begin, end) } // Default "v1" return nil, errors.New("not supported in the v1 consensus") }