From fcbc5a28e18814f01fc6ef156885f3010f4a3991 Mon Sep 17 00:00:00 2001 From: Jianrong Date: Sat, 2 Oct 2021 21:22:32 +1000 Subject: [PATCH] refactor XDPoS to accommodate multiple consensus engine versions --- .gitignore | 2 + XDCxlending/XDCxlending.go | 7 +- consensus/XDPoS/XDPoS.go | 1458 +++-------------- consensus/XDPoS/XDPoS_test.go | 13 +- consensus/XDPoS/api.go | 35 +- consensus/XDPoS/engines/engine_v1/engine.go | 1145 +++++++++++++ .../XDPoS/{ => engines/engine_v1}/snapshot.go | 71 +- consensus/XDPoS/engines/engine_v2/engine.go | 1 + consensus/XDPoS/engines/engine_v2/snapshot.go | 1 + consensus/XDPoS/utils/constants.go | 25 + consensus/XDPoS/utils/errors.go | 74 + consensus/XDPoS/utils/types.go | 58 + consensus/XDPoS/utils/utils.go | 111 ++ contracts/utils.go | 8 +- contracts/utils_test.go | 4 +- core/blockchain.go | 21 +- eth/backend.go | 29 +- go.sum | 3 + internal/ethapi/api.go | 25 +- miner/worker.go | 12 +- params/config.go | 8 + 21 files changed, 1722 insertions(+), 1389 deletions(-) create mode 100644 consensus/XDPoS/engines/engine_v1/engine.go rename consensus/XDPoS/{ => engines/engine_v1}/snapshot.go (80%) create mode 100644 consensus/XDPoS/engines/engine_v2/engine.go create mode 100644 consensus/XDPoS/engines/engine_v2/snapshot.go create mode 100644 consensus/XDPoS/utils/constants.go create mode 100644 consensus/XDPoS/utils/errors.go create mode 100644 consensus/XDPoS/utils/types.go create mode 100644 consensus/XDPoS/utils/utils.go diff --git a/.gitignore b/.gitignore index be40968a3b..6c6f16ee66 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ profile.cov /dashboard/assets/package-lock.json **/yarn-error.log +coverage.txt +go.sum \ No newline at end of file diff --git a/XDCxlending/XDCxlending.go b/XDCxlending/XDCxlending.go index 200de4bc4c..7b1d591280 100644 --- a/XDCxlending/XDCxlending.go +++ b/XDCxlending/XDCxlending.go @@ -4,6 +4,10 @@ import ( "encoding/json" "errors" "fmt" + "math/big" + "strconv" + "time" + "github.com/XinFinOrg/XDPoSChain/XDCx" "github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate" "github.com/XinFinOrg/XDPoSChain/XDCxDAO" @@ -12,9 +16,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/p2p" "gopkg.in/karalabe/cookiejar.v2/collections/prque" - "math/big" - "strconv" - "time" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/state" diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index d4b19bba84..32da29c597 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018 XDPoSChain +// 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 @@ -13,235 +13,31 @@ // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . -// Package XDPoS implements the delegated-proof-of-stake consensus engine. +// Package XDPoS is the adaptor for different consensus engine. package XDPoS import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" "math/big" - "math/rand" - "path/filepath" - "reflect" - "sort" - "strconv" - "sync" - "time" - "github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate" - "github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate" - "gopkg.in/karalabe/cookiejar.v2/collections/prque" - - "github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/clique" - "github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" - "github.com/XinFinOrg/XDPoSChain/crypto" - "github.com/XinFinOrg/XDPoSChain/crypto/sha3" "github.com/XinFinOrg/XDPoSChain/ethdb" - "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" - "github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rpc" lru "github.com/hashicorp/golang-lru" ) -const ( - inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory - blockSignersCacheLimit = 9000 - M2ByteLength = 4 -) - -type Masternode struct { - Address common.Address - Stake *big.Int -} - -type TradingService interface { - GetTradingStateRoot(block *types.Block, author common.Address) (common.Hash, error) - GetTradingState(block *types.Block, author common.Address) (*tradingstate.TradingStateDB, error) - HasTradingState(block *types.Block, author common.Address) bool - GetStateCache() tradingstate.Database - GetTriegc() *prque.Prque - ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, XDCXstatedb *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) - UpdateMediumPriceBeforeEpoch(epochNumber uint64, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB) error - IsSDKNode() bool - SyncDataToSDKNode(takerOrder *tradingstate.OrderItem, txHash common.Hash, txMatchTime time.Time, statedb *state.StateDB, trades []map[string]string, rejectedOrders []*tradingstate.OrderItem, dirtyOrderCount *uint64) error - RollbackReorgTxMatch(txhash common.Hash) error - GetTokenDecimal(chain consensus.ChainContext, statedb *state.StateDB, tokenAddr common.Address) (*big.Int, error) -} - -type LendingService interface { - GetLendingStateRoot(block *types.Block, author common.Address) (common.Hash, error) - GetLendingState(block *types.Block, author common.Address) (*lendingstate.LendingStateDB, error) - HasLendingState(block *types.Block, author common.Address) bool - GetStateCache() lendingstate.Database - GetTriegc() *prque.Prque - ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, lendingStateDB *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB, lendingOrderBook common.Hash, order *lendingstate.LendingItem) ([]*lendingstate.LendingTrade, []*lendingstate.LendingItem, error) - GetCollateralPrices(header *types.Header, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, collateralToken common.Address, lendingToken common.Address) (*big.Int, *big.Int, error) - GetMediumTradePriceBeforeEpoch(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, baseToken common.Address, quoteToken common.Address) (*big.Int, error) - ProcessLiquidationData(header *types.Header, chain consensus.ChainContext, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, lendingState *lendingstate.LendingStateDB) (updatedTrades map[common.Hash]*lendingstate.LendingTrade, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades []*lendingstate.LendingTrade, err error) - SyncDataToSDKNode(chain consensus.ChainContext, state *state.StateDB, block *types.Block, takerOrderInTx *lendingstate.LendingItem, txHash common.Hash, txMatchTime time.Time, trades []*lendingstate.LendingTrade, rejectedOrders []*lendingstate.LendingItem, dirtyOrderCount *uint64) error - UpdateLiquidatedTrade(blockTime uint64, result lendingstate.FinalizedResult, trades map[common.Hash]*lendingstate.LendingTrade) error - RollbackLendingData(txhash common.Hash) error -} - -// XDPoS delegated-proof-of-stake protocol constants. -var ( - epochLength = uint64(900) // 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 = 65 // 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 -) - -// Various error messages to mark blocks invalid. These should be private to -// prevent engine specific errors from being referenced in the remainder of the -// codebase, inherently breaking if the engine is swapped out. Please put common -// error types into the consensus package. -var ( - // errUnknownBlock is returned when the list of signers is requested for a block - // that is not part of the local blockchain. - errUnknownBlock = errors.New("unknown block") - - // errInvalidCheckpointBeneficiary is returned if a checkpoint/epoch transition - // block has a beneficiary set to non-zeroes. - errInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero") - - // errInvalidVote is returned if a nonce value is something else that the two - // allowed constants of 0x00..0 or 0xff..f. - errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f") - - // errInvalidCheckpointVote is returned if a checkpoint/epoch transition block - // has a vote nonce set to non-zeroes. - errInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero") - - // errMissingVanity is returned if a block's extra-data section is shorter than - // 32 bytes, which is required to store the signer vanity. - errMissingVanity = errors.New("extra-data 32 byte vanity prefix missing") - - // errMissingSignature is returned if a block's extra-data section doesn't seem - // to contain a 65 byte secp256k1 signature. - errMissingSignature = errors.New("extra-data 65 byte suffix signature missing") - - // errExtraSigners is returned if non-checkpoint block contain signer data in - // their extra-data fields. - errExtraSigners = errors.New("non-checkpoint block contains extra signer list") - - // errInvalidCheckpointSigners is returned if a checkpoint block contains an - // invalid list of signers (i.e. non divisible by 20 bytes, or not the correct - // ones). - errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block") - - errInvalidCheckpointPenalties = errors.New("invalid penalty list on checkpoint block") - - // errInvalidMixDigest is returned if a block's mix digest is non-zero. - errInvalidMixDigest = errors.New("non-zero mix digest") - - // errInvalidUncleHash is returned if a block contains an non-empty uncle list. - errInvalidUncleHash = errors.New("non empty uncle hash") - - // errInvalidDifficulty is returned if the difficulty of a block is not either - // of 1 or 2, or if the value does not match the turn of the signer. - errInvalidDifficulty = errors.New("invalid difficulty") - - // ErrInvalidTimestamp is returned if the timestamp of a block is lower than - // the previous block's timestamp + the minimum block period. - ErrInvalidTimestamp = errors.New("invalid timestamp") - - // errInvalidVotingChain is returned if an authorization list is attempted to - // be modified via out-of-range or non-contiguous headers. - errInvalidVotingChain = errors.New("invalid voting chain") - - // errUnauthorized is returned if a header is signed by a non-authorized entity. - errUnauthorized = errors.New("unauthorized") - - errFailedDoubleValidation = errors.New("wrong pair of creator-validator in double validation") - - // errWaitTransactions is returned if an empty block is attempted to be sealed - // on an instant chain (0 second period). It's important to refuse these as the - // block reward is zero, so an empty block just bloats the chain... fast. - errWaitTransactions = errors.New("waiting for transactions") - - ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint 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 sigHash(header *types.Header) (hash common.Hash) { - hasher := sha3.NewKeccak256() - - rlp.Encode(hasher, []interface{}{ - header.ParentHash, - header.UncleHash, - header.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short - header.MixDigest, - header.Nonce, - }) - hasher.Sum(hash[:0]) - return hash -} - func SigHash(header *types.Header) (hash common.Hash) { - return sigHash(header) -} - -// ecrecover extracts the Ethereum account address from a signed header. -func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) { - // If the signature's already cached, return that - hash := header.Hash() - if address, known := sigcache.Get(hash); known { - return address.(common.Address), nil + switch params.BlockConsensusVersion(header.Number) { + // TODO: Add switch case for 2.0 later + default: // Default "1.0" + return engine_v1.SigHash(header) } - // Retrieve the signature from the header extra-data - if len(header.Extra) < extraSeal { - return common.Address{}, errMissingSignature - } - signature := header.Extra[len(header.Extra)-extraSeal:] - - // Recover the public key and the Ethereum address - pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature) - if err != nil { - return common.Address{}, err - } - var signer common.Address - copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) - - sigcache.Add(hash, signer) - return signer, nil } // XDPoS is the delegated-proof-of-stake consensus engine proposed to support the @@ -250,25 +46,17 @@ type XDPoS struct { config *params.XDPoSConfig // Consensus engine configuration parameters db ethdb.Database // Database to store and retrieve snapshot checkpoints - recents *lru.ARCCache // Snapshots for recent block to speed up reorgs - signatures *lru.ARCCache // Signatures of recent blocks to speed up mining - validatorSignatures *lru.ARCCache // Signatures of recent blocks to speed up mining - verifiedHeaders *lru.ARCCache - 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 - BlockSigners *lru.Cache HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (error, map[string]interface{}) 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 - GetXDCXService func() TradingService - GetLendingService func() LendingService + GetXDCXService func() utils.TradingService + GetLendingService func() utils.LendingService HookGetSignersFromContract func(blockHash common.Hash) ([]common.Address, error) + + EngineV1 engine_v1.XDPoS_v1 } // New creates a XDPoS delegated-proof-of-stake consensus engine with the initial @@ -277,854 +65,26 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS { // Set any missing consensus parameters to their defaults conf := *config if conf.Epoch == 0 { - conf.Epoch = epochLength + conf.Epoch = utils.EpochLength } + // Allocate the snapshot caches and create the engine - BlockSigners, _ := lru.New(blockSignersCacheLimit) - recents, _ := lru.NewARC(inmemorySnapshots) - signatures, _ := lru.NewARC(inmemorySnapshots) - validatorSignatures, _ := lru.NewARC(inmemorySnapshots) - verifiedHeaders, _ := lru.NewARC(inmemorySnapshots) + BlockSigners, _ := lru.New(utils.BlockSignersCacheLimit) + return &XDPoS{ - config: &conf, - db: db, - BlockSigners: BlockSigners, - recents: recents, - signatures: signatures, - verifiedHeaders: verifiedHeaders, - validatorSignatures: validatorSignatures, - proposals: make(map[common.Address]bool), + config: &conf, + + BlockSigners: BlockSigners, + EngineV1: *engine_v1.New(&conf, db), } } // 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) *XDPoS { - var fakeEngine *XDPoS - // Set any missing consensus parameters to their defaults - conf := params.TestXDPoSMockChainConfig.XDPoS - - // Allocate the snapshot caches and create the engine - BlockSigners, _ := lru.New(blockSignersCacheLimit) - recents, _ := lru.NewARC(inmemorySnapshots) - signatures, _ := lru.NewARC(inmemorySnapshots) - validatorSignatures, _ := lru.NewARC(inmemorySnapshots) - verifiedHeaders, _ := lru.NewARC(inmemorySnapshots) - fakeEngine = &XDPoS{ - config: conf, - db: db, - BlockSigners: BlockSigners, - recents: recents, - signatures: signatures, - verifiedHeaders: verifiedHeaders, - validatorSignatures: validatorSignatures, - proposals: make(map[common.Address]bool), - } - return fakeEngine -} - -// Author implements consensus.Engine, returning the Ethereum address recovered -// from the signature in the header's extra-data section. -func (c *XDPoS) Author(header *types.Header) (common.Address, error) { - return ecrecover(header, c.signatures) -} - -// Get signer coinbase -func (c *XDPoS) Signer() common.Address { return c.signer } - -// VerifyHeader checks whether a header conforms to the consensus rules. -func (c *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { - return c.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 (c *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) { - abort := make(chan struct{}) - results := make(chan error, len(headers)) - - go func() { - for i, header := range headers { - err := c.verifyHeaderWithCache(chain, header, headers[:i], fullVerifies[i]) - - select { - case <-abort: - return - case results <- err: - } - } - }() - return abort, results -} - -func (c *XDPoS) verifyHeaderWithCache(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error { - _, check := c.verifiedHeaders.Get(header.Hash()) - if check { - return nil - } - err := c.verifyHeader(chain, header, parents, fullVerify) - if err == nil { - c.verifiedHeaders.Add(header.Hash(), true) - } - 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 (c *XDPoS) 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 c.config.SkipValidation { - return nil - } - if common.IsTestnet { - fullVerify = false - } - if header.Number == nil { - return errUnknownBlock - } - number := header.Number.Uint64() - if fullVerify { - if header.Number.Uint64() > c.config.Epoch && len(header.Validator) == 0 { - return consensus.ErrNoValidatorSignature - } - // Don't waste time checking blocks from the future - if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 { - return consensus.ErrFutureBlock - } - } - // Checkpoint blocks need to enforce zero beneficiary - checkpoint := (number % c.config.Epoch) == 0 - if checkpoint && header.Coinbase != (common.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%common.AddressLength != 0 { - return errInvalidCheckpointSigners - } - // Ensure that the mix digest is zero as we don't have fork protection currently - if header.MixDigest != (common.Hash{}) { - return errInvalidMixDigest - } - // Ensure that the block doesn't contain any uncles which are meaningless in XDPoS - if header.UncleHash != uncleHash { - return errInvalidUncleHash - } - // If all checks passed, validate any special fields for hard forks - if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil { - return err - } - // All basic checks passed, verify cascading fields - return c.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 (c *XDPoS) 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.Uint64()+c.config.Period > header.Time.Uint64() { - return ErrInvalidTimestamp - } - - if number%c.config.Epoch != 0 { - return c.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 := c.snapshot(chain, number-1, header.ParentHash, parents) - if err != nil { - return err - } - - signers := snap.GetSigners() - err = c.checkSignersOnCheckpoint(chain, header, signers) - if err == nil { - return c.verifySeal(chain, header, parents, fullVerify) - } - - signers, err = c.GetSignersFromContract(chain, header) - if err != nil { - return err - } - err = c.checkSignersOnCheckpoint(chain, header, signers) - if err == nil { - return c.verifySeal(chain, header, parents, fullVerify) - } - - return err -} - -func (c *XDPoS) checkSignersOnCheckpoint(chain consensus.ChainReader, header *types.Header, signers []common.Address) error { - number := header.Number.Uint64() - // ignore signerCheck at checkpoint block 14458500 due to wrong snapshot at gap 14458495 - if number == common.IgnoreSignerCheckBlock { - return nil - } - penPenalties := []common.Address{} - if c.HookPenalty != nil || c.HookPenaltyTIPSigning != nil { - var err error - if chain.Config().IsTIPSigning(header.Number) { - penPenalties, err = c.HookPenaltyTIPSigning(chain, header, signers) - } else { - penPenalties, err = c.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 errInvalidCheckpointPenalties - } - } - signers = common.RemoveItemFromArray(signers, penPenalties) - for i := 1; i <= common.LimitPenaltyEpoch; i++ { - if number > uint64(i)*c.config.Epoch { - signers = RemovePenaltiesFromBlock(chain, signers, number-uint64(i)*c.config.Epoch) - } - } - extraSuffix := len(header.Extra) - extraSeal - masternodesFromCheckpointHeader := common.ExtractAddressFromBytes(header.Extra[extraVanity:extraSuffix]) - validSigners := compareSignersLists(masternodesFromCheckpointHeader, signers) - - if !validSigners { - log.Error("Masternodes lists are different in checkpoint header and snapshot", "number", number, "masternodes_from_checkpoint_header", masternodesFromCheckpointHeader, "masternodes_in_snapshot", signers, "penList", penPenalties) - return errInvalidCheckpointSigners - } - if c.HookVerifyMNs != nil { - err := c.HookVerifyMNs(header, signers) - if err != nil { - return err - } - } - - return nil -} - -// compare 2 signers lists -// return true if they are same elements, otherwise return false -func compareSignersLists(list1 []common.Address, list2 []common.Address) bool { - if len(list1) == 0 && len(list2) == 0 { - return true - } - sort.Slice(list1, func(i, j int) bool { - return list1[i].String() <= list1[j].String() - }) - sort.Slice(list2, func(i, j int) bool { - return list2[i].String() <= list2[j].String() - }) - return reflect.DeepEqual(list1, list2) -} - -func (c *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*Snapshot, error) { - number := header.Number.Uint64() - log.Trace("take snapshot", "number", number, "hash", header.Hash()) - snap, err := c.snapshot(chain, number, header.Hash(), nil) - if err != nil { - return nil, err - } - return snap, nil -} - -func (c *XDPoS) StoreSnapshot(snap *Snapshot) error { - return snap.store(c.db) -} - -func position(list []common.Address, x common.Address) int { - for i, item := range list { - if item == x { - return i - } - } - return -1 -} - -func (c *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { - n := header.Number.Uint64() - e := c.config.Epoch - switch { - case n%e == 0: - return c.GetMasternodesFromCheckpointHeader(header, n, e) - case n%e != 0: - h := chain.GetHeaderByNumber(n - (n % e)) - return c.GetMasternodesFromCheckpointHeader(h, n, e) - default: - return []common.Address{} - } -} - -func (c *XDPoS) GetPeriod() uint64 { return c.config.Period } - -func whoIsCreator(snap *Snapshot, 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 (c *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) { - masternodes := c.GetMasternodes(chain, parent) - - // if common.IsTestnet { - // // Only three mns hard code for XDC testnet. - // masternodes = []common.Address{ - // common.HexToAddress("0x3Ea0A3555f9B1dE983572BfF6444aeb1899eC58C"), - // common.HexToAddress("0x4F7900282F3d371d585ab1361205B0940aB1789C"), - // common.HexToAddress("0x942a5885A8844Ee5587C8AC5e371Fc39FFE61896"), - // } - // } - - snap, err := c.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 = whoIsCreator(snap, parent) - if err != nil { - return 0, 0, 0, false, err - } - preIndex = position(masternodes, pre) - } - curIndex := position(masternodes, signer) - if signer == c.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.String()) - } - 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 (c *XDPoS) snapshot(chain consensus.ChainReader, number uint64, hash common.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.(*Snapshot) - break - } - // If an on-disk checkpoint snapshot can be found, use that - // checkpoint snapshot = checkpoint - gap - if (number+c.config.Gap)%c.config.Epoch == 0 { - if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { - log.Trace("Loaded voting snapshot form 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 := c.VerifyHeader(chain, genesis, true); err != nil { - return nil, err - } - signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength) - for i := 0; i < len(signers); i++ { - copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:]) - } - snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers) - if err := snap.store(c.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 { - // 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 - } - c.recents.Add(snap.Hash, snap) - - // If we've generated a new checkpoint snapshot, save to disk - if (snap.Number+c.config.Gap)%c.config.Epoch == 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 -} - -// VerifyUncles implements consensus.Engine, always returning an error for any -// uncles as this consensus mechanism doesn't permit uncles. -func (c *XDPoS) 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 (c *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error { - return c.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 (c *XDPoS) 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 errUnknownBlock - } - // 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 - } - - // Resolve the authorization key and check against signers - creator, err := ecrecover(header, c.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 := c.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 errInvalidDifficulty - } - } - masternodes := c.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.String(), "masternodes", mstring, "snapshot from parent block", nstring) - return 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%c.config.Epoch != 0 { - return errUnauthorized - } - } - } - } - } - - // header must contain validator info following double validation design - // start checking from epoch 2nd. - if header.Number.Uint64() > c.config.Epoch && fullVerify { - validator, err := c.RecoverValidator(header) - if err != nil { - return err - } - - // verify validator - assignedValidator, err := c.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 errFailedDoubleValidation - } - } - return nil -} - -func (c *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) { - epoch := c.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{}, fmt.Errorf("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 (c *XDPoS) 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 := c.snapshot(chain, number-1, header.ParentHash, nil) - if err != nil { - return err - } - if number%c.config.Epoch != 0 { - c.lock.RLock() - - // Gather all the proposals that make sense voting on - addresses := make([]common.Address, 0, len(c.proposals)) - for address, authorize := range c.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 c.proposals[header.Coinbase] { - copy(header.Nonce[:], nonceAuthVote) - } else { - copy(header.Nonce[:], nonceDropVote) - } - } - c.lock.RUnlock() - } - parent := chain.GetHeader(header.ParentHash, number-1) - if parent == nil { - return consensus.ErrUnknownAncestor - } - // Set the correct difficulty - header.Difficulty = c.calcDifficulty(chain, parent, c.signer) - log.Debug("CalcDifficulty ", "number", header.Number, "difficulty", header.Difficulty) - // Ensure the extra data has all it's components - if len(header.Extra) < extraVanity { - header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) - } - header.Extra = header.Extra[:extraVanity] - masternodes := snap.GetSigners() - if number >= c.config.Epoch && number%c.config.Epoch == 0 { - if c.HookPenalty != nil || c.HookPenaltyTIPSigning != nil { - var penMasternodes []common.Address = nil - var err error = nil - if chain.Config().IsTIPSigning(header.Number) { - penMasternodes, err = c.HookPenaltyTIPSigning(chain, header, masternodes) - } else { - penMasternodes, err = c.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)*c.config.Epoch { - masternodes = RemovePenaltiesFromBlock(chain, masternodes, number-uint64(i)*c.config.Epoch) - } - } - for _, masternode := range masternodes { - header.Extra = append(header.Extra, masternode[:]...) - } - if c.HookValidator != nil { - validators, err := c.HookValidator(header, masternodes) - if err != nil { - return err - } - header.Validators = validators - } - } - header.Extra = append(header.Extra, make([]byte, extraSeal)...) - - // Mix digest is reserved for now, set to empty - header.MixDigest = common.Hash{} - - // Ensure the timestamp has the correct delay - - header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period)) - if header.Time.Int64() < time.Now().Unix() { - header.Time = big.NewInt(time.Now().Unix()) - } - return nil -} - -func (c *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []Masternode) error { - number := header.Number.Uint64() - log.Trace("take snapshot", "number", number, "hash", header.Hash()) - // get snapshot - snap, err := c.snapshot(chain, number, header.Hash(), nil) - 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()) - } - c.recents.Add(snap.Hash, snap) - log.Info("New set of masternodes has been updated to snapshot", "number", snap.Number, "hash", snap.Hash, "new masternodes", nm) - return nil -} - -// Finalize implements consensus.Engine, ensuring no uncles are set, nor block -// rewards given, and returns the final block. -func (c *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.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 c.HookReward != nil && number%rCheckpoint == 0 { - err, rewards := c.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 = ioutil.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, txs, nil, receipts), nil -} - -// Authorize injects a private key into the consensus engine to mint new blocks -// with. -func (c *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) { - c.lock.Lock() - defer c.lock.Unlock() - - c.signer = signer - c.signFn = signFn -} - -// Seal implements consensus.Engine, attempting to create a sealed block using -// the local signing credentials. -func (c *XDPoS) 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, errUnknownBlock - } - // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) - // checkpoint blocks have no tx - if c.config.Period == 0 && len(block.Transactions()) == 0 && number%c.config.Epoch != 0 { - return nil, errWaitTransactions - } - // Don't hold the signer fields for the entire sealing procedure - c.lock.RLock() - signer, signFn := c.signer, c.signFn - c.lock.RUnlock() - - // Bail out if we're unauthorized to sign a block - snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) - if err != nil { - return nil, err - } - masternodes := c.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, 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%c.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}, sigHash(header).Bytes()) - if err != nil { - return nil, err - } - copy(header.Extra[len(header.Extra)-extraSeal:], sighash) - m2, err := c.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 (c *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { - return c.calcDifficulty(chain, parent, c.signer) -} - -func (c *XDPoS) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int { - // If we're running a engine faking, skip calculation - if c.config.SkipValidation { - return big.NewInt(1) - } - len, preIndex, curIndex, _, err := c.YourTurn(chain, parent, signer) - if err != nil { - return big.NewInt(int64(len + curIndex - preIndex)) - } - return big.NewInt(int64(len - Hop(len, preIndex, curIndex))) -} +/* + Eth Consensus engine interface implementation +*/ // APIs implements consensus.Engine, returning the user facing RPC API to allow // controlling the signer voting. func (c *XDPoS) APIs(chain consensus.ChainReader) []rpc.API { @@ -1136,227 +96,207 @@ func (c *XDPoS) APIs(chain consensus.ChainReader) []rpc.API { }} } +// Author implements consensus.Engine, returning the Ethereum address recovered +// from the signature in the header's extra-data section. +func (c *XDPoS) Author(header *types.Header) (common.Address, error) { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.Author(header) + } +} + +// VerifyHeader checks whether a header conforms to the consensus rules. +func (c *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.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 (c *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) { + // TODO: (Hashlab) This funciton is a special case + return c.EngineV1.VerifyHeaders(chain, headers, fullVerifies) +} + +// VerifyUncles implements consensus.Engine, always returning an error for any +// uncles as this consensus mechanism doesn't permit uncles. +func (c *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + switch params.BlockConsensusVersion(block.Number()) { + default: // Default "1.0" + return c.EngineV1.VerifyUncles(chain, block) + } +} + +// VerifySeal implements consensus.Engine, checking whether the signature contained +// in the header satisfies the consensus protocol requirements. +func (c *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.VerifySeal(chain, header) + } +} + +// Prepare implements consensus.Engine, preparing all the consensus fields of the +// header for running the transactions on top. +func (c *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.Prepare(chain, header) + } +} + +// Finalize implements consensus.Engine, ensuring no uncles are set, nor block +// rewards given, and returns the final block. +func (c *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.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 (c *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { + switch params.BlockConsensusVersion(block.Number()) { + default: // Default "1.0" + return c.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 (c *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { + switch params.BlockConsensusVersion(parent.Number) { + default: // Default "1.0" + return c.EngineV1.CalcDifficulty(chain, time, parent) + } +} + +/* + XDC specific methods +*/ + +// Authorize injects a private key into the consensus engine to mint new blocks +// with. +func (c *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) { + // Authorize each consensus individually + c.EngineV1.Authorize(signer, signFn) +} + +func (c *XDPoS) GetPeriod() uint64 { + return c.config.Period +} + +func (c *XDPoS) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.IsAuthorisedAddress(header, chain, address) + } +} + +func (c *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.GetMasternodes(chain, header) + } +} + +func (c *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) { + switch params.BlockConsensusVersion(parent.Number) { + default: // Default "1.0" + return c.EngineV1.YourTurn(chain, parent, signer) + } +} + +func (c *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.GetValidator(creator, chain, header) + } +} + +func (c *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.UpdateMasternodes(chain, header, ms) + } +} + func (c *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) { - return ecrecover(header, c.signatures) + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.RecoverSigner(header) + } } func (c *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) { - // If the signature's already cached, return that - hash := header.Hash() - if address, known := c.validatorSignatures.Get(hash); known { - return address.(common.Address), nil + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.RecoverValidator(header) } - // Retrieve the signature from the header.Validator - // len equals 65 bytes - if len(header.Validator) != extraSeal { - return common.Address{}, consensus.ErrFailValidatorSignature - } - // Recover the public key and the Ethereum address - pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), header.Validator) - if err != nil { - return common.Address{}, err - } - var signer common.Address - copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) - - c.validatorSignatures.Add(hash, signer) - return signer, nil } // Get master nodes over extra data of previous checkpoint block. func (c *XDPoS) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address { - if preCheckpointHeader == nil { - log.Info("Previous checkpoint's header is empty", "block number", n, "epoch", e) - return []common.Address{} + switch params.BlockConsensusVersion(preCheckpointHeader.Number) { + default: // Default "1.0" + return c.EngineV1.GetMasternodesFromCheckpointHeader(preCheckpointHeader, n, e) } - masternodes := make([]common.Address, (len(preCheckpointHeader.Extra)-extraVanity-extraSeal)/common.AddressLength) - for i := 0; i < len(masternodes); i++ { - copy(masternodes[i][:], preCheckpointHeader.Extra[extraVanity+i*common.AddressLength:]) - } - - return masternodes } func (c *XDPoS) CacheData(header *types.Header, txs []*types.Transaction, receipts []*types.Receipt) []*types.Transaction { - signTxs := []*types.Transaction{} - for _, tx := range txs { - if tx.IsSigningTransaction() { - var b uint - 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) - } + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.CacheData(header, txs, receipts) } - - log.Debug("Save tx signers to cache", "hash", header.Hash().String(), "number", header.Number, "len(txs)", len(signTxs)) - c.BlockSigners.Add(header.Hash(), signTxs) - - return signTxs -} - -func (c *XDPoS) CacheSigner(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.String(), "len(txs)", len(signTxs)) - c.BlockSigners.Add(hash, signTxs) - return signTxs } +// Same DB across all consensus engines func (c *XDPoS) GetDb() ethdb.Database { return c.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 -} - -// Get masternodes address from checkpoint Header. -func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address { - masternodes := make([]common.Address, (len(checkpointHeader.Extra)-extraVanity-extraSeal)/common.AddressLength) - for i := 0; i < len(masternodes); i++ { - copy(masternodes[i][:], checkpointHeader.Extra[extraVanity+i*common.AddressLength:]) - } - return masternodes -} - -// Get m2 list from checkpoint block. -func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) { - if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 { - return nil, errors.New("This block is not checkpoint block epoc.") - } - // Get signers from this block. - masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader) - validators := ExtractValidatorsFromBytes(checkpointHeader.Validators) - m1m2, _, err := getM1M2(masternodes, validators, currentHeader, config) - if err != nil { - return map[common.Address]common.Address{}, err - } - return m1m2, nil -} - -func getM1M2(masternodes []common.Address, validators []int64, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, uint64, error) { - m1m2 := map[common.Address]common.Address{} - maxMNs := len(masternodes) - moveM2 := uint64(0) - if len(validators) < maxMNs { - return nil, moveM2, errors.New("len(m2) is less than len(m1)") - } - if maxMNs > 0 { - isForked := config.IsTIPRandomize(currentHeader.Number) - if isForked { - moveM2 = ((currentHeader.Number.Uint64() % config.XDPoS.Epoch) / uint64(maxMNs)) % uint64(maxMNs) - } - for i, m1 := range masternodes { - m2Index := uint64(validators[i] % int64(maxMNs)) - m2Index = (m2Index + moveM2) % uint64(maxMNs) - m1m2[m1] = masternodes[m2Index] - } - } - return m1m2, moveM2, nil -} - -// Extract validators from byte array. -func ExtractValidatorsFromBytes(byteValidators []byte) []int64 { - lenValidator := len(byteValidators) / M2ByteLength - var validators []int64 - for i := 0; i < lenValidator; i++ { - trimByte := bytes.Trim(byteValidators[i*M2ByteLength:(i+1)*M2ByteLength], "\x00") - intNumber, err := strconv.Atoi(string(trimByte)) - if err != nil { - log.Error("Can not convert string to integer", "error", err) - return []int64{} - } - validators = append(validators, int64(intNumber)) - } - - return validators -} - -func Hop(len, pre, cur int) int { - switch { - case pre < cur: - return cur - (pre + 1) - case pre > cur: - return (len - pre) + (cur - 1) - default: - return len - 1 +func (c *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + sp, err := c.EngineV1.GetSnapshot(chain, header) + // 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 (c *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) { + switch params.BlockConsensusVersion(header.Number) { + default: // Default "1.0" + return c.EngineV1.GetAuthorisedSignersFromSnapshot(chain, header) + } +} + +// TODO: (Hashlab) Can be further refactored func (c *XDPoS) CheckMNTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) bool { - masternodes := c.GetMasternodes(chain, parent) - - if common.IsTestnet { - // Only three mns hard code for XDC testnet. - masternodes = []common.Address{ - common.HexToAddress("0x3Ea0A3555f9B1dE983572BfF6444aeb1899eC58C"), - common.HexToAddress("0x4F7900282F3d371d585ab1361205B0940aB1789C"), - common.HexToAddress("0x942a5885A8844Ee5587C8AC5e371Fc39FFE61896"), - } + switch params.BlockConsensusVersion(parent.Number) { + default: // Default "1.0" + return c.EngineV1.CheckMNTurn(chain, parent, signer) } - - snap, err := c.GetSnapshot(chain, parent) - if err != nil { - log.Warn("Failed when trying to commit new work", "err", err) - return false - } - if len(masternodes) == 0 { - return false - } - pre := common.Address{} - // masternode[0] has chance to create block 1 - preIndex := -1 - if parent.Number.Uint64() != 0 { - pre, err = whoIsCreator(snap, parent) - if err != nil { - return false - } - preIndex = position(masternodes, pre) - } - curIndex := position(masternodes, signer) - if (preIndex)%len(masternodes) == curIndex { - return true - } - return false } -func (c *XDPoS) 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 := c.HookGetSignersFromContract(startGapBlockHeader.Hash()) - if err != nil { - return []common.Address{}, fmt.Errorf("Can't get signers from Smart Contract . Err: %v", err) - } - return signers, nil +// TODO: (Hashlab) Need further work on refactor this method +func (c *XDPoS) CacheSigner(hash common.Hash, txs []*types.Transaction) []*types.Transaction { + return c.EngineV1.CacheSigner(hash, txs) +} + +// TODO: (Hashlab)Get signer coinbase +func (c *XDPoS) Signer() common.Address { + return c.EngineV1.Signer() } diff --git a/consensus/XDPoS/XDPoS_test.go b/consensus/XDPoS/XDPoS_test.go index e00d3eed03..4d16514a00 100644 --- a/consensus/XDPoS/XDPoS_test.go +++ b/consensus/XDPoS/XDPoS_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/params" ) @@ -34,7 +35,7 @@ func TestGetM1M2FromCheckpointHeader(t *testing.T) { currentHeader := &types.Header{ Number: big.NewInt(currentNumber), } - m1m2, moveM2, err := getM1M2(masternodes, validators, currentHeader, config) + m1m2, moveM2, err := utils.GetM1M2(masternodes, validators, currentHeader, config) if err != nil { t.Error("can't get m1m2", "err", err) } @@ -66,19 +67,19 @@ func TestCompareSignersLists(t *testing.T) { common.StringToAddress("dddddddddddddddddddddddddddddddddddddddd"), common.StringToAddress("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), } - if !compareSignersLists(list1, list2) { + if !utils.CompareSignersLists(list1, list2) { t.Error("list1 should be equal to list2", "list1", list1, "list2", list2) } - if compareSignersLists(list1, list3) { + if utils.CompareSignersLists(list1, list3) { t.Error("list1 and list3 should not be same", "list1", list1, "list3", list3) } - if !compareSignersLists([]common.Address{}, []common.Address{}) { + if !utils.CompareSignersLists([]common.Address{}, []common.Address{}) { t.Error("Failed with empty list") } - if !compareSignersLists([]common.Address{common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc")}, []common.Address{common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc")}) { + if !utils.CompareSignersLists([]common.Address{common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc")}, []common.Address{common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc")}) { t.Error("Failed with list has only one signer") } - if compareSignersLists([]common.Address{common.StringToAddress("aaaaaaaaaaaaaaaa")}, []common.Address{common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc")}) { + if utils.CompareSignersLists([]common.Address{common.StringToAddress("aaaaaaaaaaaaaaaa")}, []common.Address{common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc")}) { t.Error("Failed with list has only one signer") } } diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 252ad07ff1..3db252bb15 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -16,11 +16,13 @@ package XDPoS import ( + "math/big" + "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/rpc" - "math/big" ) // API is a user facing RPC API to allow controlling the signer and voting @@ -39,7 +41,7 @@ type NetworkInformation struct { } // GetSnapshot retrieves the state snapshot at a given block. -func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { +func (api *API) GetSnapshot(number *rpc.BlockNumber) (*utils.PublicApiSnapshot, error) { // Retrieve the requested block number (or current if none requested) var header *types.Header if number == nil || *number == rpc.LatestBlockNumber { @@ -49,18 +51,18 @@ func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { } // Ensure we have an actually valid block and return its snapshot if header == nil { - return nil, errUnknownBlock + return nil, utils.ErrUnknownBlock } - return api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + return api.XDPoS.GetSnapshot(api.chain, header) } // GetSnapshotAtHash retrieves the state snapshot at a given block. -func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) { +func (api *API) GetSnapshotAtHash(hash common.Hash) (*utils.PublicApiSnapshot, error) { header := api.chain.GetHeaderByHash(hash) if header == nil { - return nil, errUnknownBlock + return nil, utils.ErrUnknownBlock } - return api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + return api.XDPoS.GetSnapshot(api.chain, header) } // GetSigners retrieves the list of authorized signers at the specified block. @@ -74,31 +76,22 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) { } // Ensure we have an actually valid block and return the signers from its snapshot if header == nil { - return nil, errUnknownBlock + return nil, utils.ErrUnknownBlock } - snap, err := api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) - if err != nil { - return nil, err - } - return snap.GetSigners(), nil + + return api.XDPoS.GetAuthorisedSignersFromSnapshot(api.chain, header) } // GetSignersAtHash retrieves the state snapshot at a given block. func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) { header := api.chain.GetHeaderByHash(hash) if header == nil { - return nil, errUnknownBlock + return nil, utils.ErrUnknownBlock } - snap, err := api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) - if err != nil { - return nil, err - } - return snap.GetSigners(), nil + return api.XDPoS.GetAuthorisedSignersFromSnapshot(api.chain, header) } func (api *API) NetworkInformation() NetworkInformation { - api.XDPoS.lock.RLock() - defer api.XDPoS.lock.RUnlock() info := NetworkInformation{} info.NetworkId = api.chain.Config().ChainId info.XDCValidatorAddress = common.HexToAddress(common.MasternodeVotingSMC) diff --git a/consensus/XDPoS/engines/engine_v1/engine.go b/consensus/XDPoS/engines/engine_v1/engine.go new file mode 100644 index 0000000000..ec1be48a11 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v1/engine.go @@ -0,0 +1,1145 @@ +package engine_v1 + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "math/big" + "math/rand" + "path/filepath" + "sync" + "time" + + "github.com/XinFinOrg/XDPoSChain/accounts" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/consensus/clique" + "github.com/XinFinOrg/XDPoSChain/consensus/misc" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/crypto/sha3" + "github.com/XinFinOrg/XDPoSChain/ethdb" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/XinFinOrg/XDPoSChain/rlp" + lru "github.com/hashicorp/golang-lru" +) + +// 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 sigHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewKeccak256() + + err := rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short + header.MixDigest, + header.Nonce, + }) + if err != nil { + log.Debug("Fail to encode", err) + } + hasher.Sum(hash[:0]) + return hash +} + +func SigHash(header *types.Header) (hash common.Hash) { + return sigHash(header) +} + +// ecrecover extracts the Ethereum account address from a signed header. +func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) { + // If the signature's already cached, return that + hash := header.Hash() + if address, known := sigcache.Get(hash); known { + return address.(common.Address), nil + } + // Retrieve the signature from the header extra-data + if len(header.Extra) < utils.ExtraSeal { + return common.Address{}, utils.ErrMissingSignature + } + signature := header.Extra[len(header.Extra)-utils.ExtraSeal:] + + // Recover the public key and the Ethereum address + pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature) + if err != nil { + return common.Address{}, err + } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + + sigcache.Add(hash, signer) + return signer, nil +} + +// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the +// Ethereum testnet following the Ropsten attacks. +type XDPoS_v1 struct { + config *params.XDPoSConfig // Consensus engine configuration parameters + db ethdb.Database // Database to store and retrieve snapshot checkpoints + + recents *lru.ARCCache // Snapshots for recent block to speed up reorgs + signatures *lru.ARCCache // Signatures of recent blocks to speed up mining + validatorSignatures *lru.ARCCache // Signatures of recent blocks to speed up mining + verifiedHeaders *lru.ARCCache + 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 + + BlockSigners *lru.Cache + HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (error, map[string]interface{}) + 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 + // GetXDCXService func() utils.TradingService + // GetLendingService func() utils.LendingService + HookGetSignersFromContract func(blockHash common.Hash) ([]common.Address, error) +} + +// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial +// signers set to the ones provided by the user. +func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 { + // Set any missing consensus parameters to their defaults + conf := *config + if conf.Epoch == 0 { + conf.Epoch = utils.EpochLength + } + // Allocate the snapshot caches and create the engine + BlockSigners, _ := lru.New(utils.BlockSignersCacheLimit) + recents, _ := lru.NewARC(utils.InmemorySnapshots) + signatures, _ := lru.NewARC(utils.InmemorySnapshots) + validatorSignatures, _ := lru.NewARC(utils.InmemorySnapshots) + verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots) + return &XDPoS_v1{ + config: &conf, + db: db, + BlockSigners: BlockSigners, + recents: recents, + signatures: signatures, + verifiedHeaders: verifiedHeaders, + validatorSignatures: validatorSignatures, + 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 (c *XDPoS_v1) Author(header *types.Header) (common.Address, error) { + return ecrecover(header, c.signatures) +} + +// Get signer coinbase +func (c *XDPoS_v1) Signer() common.Address { return c.signer } + +// VerifyHeader checks whether a header conforms to the consensus rules. +func (c *XDPoS_v1) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { + return c.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 (c *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + results := make(chan error, len(headers)) + + go func() { + for i, header := range headers { + err := c.verifyHeaderWithCache(chain, header, headers[:i], fullVerifies[i]) + + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +func (c *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error { + _, check := c.verifiedHeaders.Get(header.Hash()) + if check { + return nil + } + err := c.verifyHeader(chain, header, parents, fullVerify) + if err == nil { + c.verifiedHeaders.Add(header.Hash(), true) + } + 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 (c *XDPoS_v1) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error { + if common.IsTestnet { + fullVerify = false + } + if header.Number == nil { + return utils.ErrUnknownBlock + } + number := header.Number.Uint64() + if fullVerify { + if header.Number.Uint64() > c.config.Epoch && len(header.Validator) == 0 { + return consensus.ErrNoValidatorSignature + } + // Don't waste time checking blocks from the future + if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 { + return consensus.ErrFutureBlock + } + } + // Checkpoint blocks need to enforce zero beneficiary + checkpoint := (number % c.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 + } + // If all checks passed, validate any special fields for hard forks + if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil { + return err + } + // All basic checks passed, verify cascading fields + return c.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 (c *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.Uint64()+c.config.Period > header.Time.Uint64() { + return utils.ErrInvalidTimestamp + } + + if number%c.config.Epoch != 0 { + return c.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 := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + + signers := snap.GetSigners() + err = c.checkSignersOnCheckpoint(chain, header, signers) + if err == nil { + return c.verifySeal(chain, header, parents, fullVerify) + } + + signers, err = c.getSignersFromContract(chain, header) + if err != nil { + return err + } + err = c.checkSignersOnCheckpoint(chain, header, signers) + if err == nil { + return c.verifySeal(chain, header, parents, fullVerify) + } + + return err +} + +func (c *XDPoS_v1) checkSignersOnCheckpoint(chain consensus.ChainReader, header *types.Header, signers []common.Address) error { + number := header.Number.Uint64() + // ignore signerCheck at checkpoint block 14458500 due to wrong snapshot at gap 14458495 + if number == common.IgnoreSignerCheckBlock { + return nil + } + penPenalties := []common.Address{} + if c.HookPenalty != nil || c.HookPenaltyTIPSigning != nil { + var err error + if chain.Config().IsTIPSigning(header.Number) { + penPenalties, err = c.HookPenaltyTIPSigning(chain, header, signers) + } else { + penPenalties, err = c.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)*c.config.Epoch { + signers = removePenaltiesFromBlock(chain, signers, number-uint64(i)*c.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, "masternodes_from_checkpoint_header", masternodesFromCheckpointHeader, "masternodes_in_snapshot", signers, "penList", penPenalties) + return utils.ErrInvalidCheckpointSigners + } + if c.HookVerifyMNs != nil { + err := c.HookVerifyMNs(header, signers) + if err != nil { + return err + } + } + + return nil +} + +func (c *XDPoS_v1) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool { + snap, err := c.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 (c *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 := c.snapshot(chain, number, header.Hash(), nil) + if err != nil { + return nil, err + } + return snap, nil +} + +func (c *XDPoS_v1) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) { + snap, err := c.GetSnapshot(chain, header) + if err != nil { + return nil, err + } + return snap.GetSigners(), nil +} + +func (c *XDPoS_v1) StoreSnapshot(snap *SnapshotV1) error { + return snap.store(c.db) +} + +func position(list []common.Address, x common.Address) int { + for i, item := range list { + if item == x { + return i + } + } + return -1 +} + +func (c *XDPoS_v1) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { + n := header.Number.Uint64() + e := c.config.Epoch + switch { + case n%e == 0: + return c.GetMasternodesFromCheckpointHeader(header, n, e) + case n%e != 0: + h := chain.GetHeaderByNumber(n - (n % e)) + return c.GetMasternodesFromCheckpointHeader(h, n, e) + default: + return []common.Address{} + } +} + +func (c *XDPoS_v1) GetPeriod() uint64 { return c.config.Period } + +func 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 (c *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) { + masternodes := c.GetMasternodes(chain, parent) + + // if common.IsTestnet { + // // Only three mns hard code for XDC testnet. + // masternodes = []common.Address{ + // common.HexToAddress("0x3Ea0A3555f9B1dE983572BfF6444aeb1899eC58C"), + // common.HexToAddress("0x4F7900282F3d371d585ab1361205B0940aB1789C"), + // common.HexToAddress("0x942a5885A8844Ee5587C8AC5e371Fc39FFE61896"), + // } + // } + + snap, err := c.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 = whoIsCreator(snap, parent) + if err != nil { + return 0, 0, 0, false, err + } + preIndex = position(masternodes, pre) + } + curIndex := position(masternodes, signer) + if signer == c.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.String()) + } + 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 (c *XDPoS_v1) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*SnapshotV1, error) { + // Search for a SnapshotV1 in memory or on disk for checkpoints + var ( + headers []*types.Header + snap *SnapshotV1 + ) + for snap == nil { + // If an in-memory SnapshotV1 was found, use that + if s, ok := c.recents.Get(hash); ok { + snap = s.(*SnapshotV1) + break + } + // If an on-disk checkpoint snapshot can be found, use that + // checkpoint snapshot = checkpoint - gap + if (number+c.config.Gap)%c.config.Epoch == 0 { + if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { + log.Trace("Loaded voting snapshot form 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 := c.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(c.config, c.signatures, 0, genesis.Hash(), signers) + if err := snap.store(c.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 { + // 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 + } + c.recents.Add(snap.Hash, snap) + + // If we've generated a new checkpoint snapshot, save to disk + if (snap.Number+c.config.Gap)%c.config.Epoch == 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 +} + +// VerifyUncles implements consensus.Engine, always returning an error for any +// uncles as this consensus mechanism doesn't permit uncles. +func (c *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 (c *XDPoS_v1) VerifySeal(chain consensus.ChainReader, header *types.Header) error { + return c.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 (c *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 := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + + // Resolve the authorization key and check against signers + creator, err := ecrecover(header, c.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 := c.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 := c.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.String(), "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%c.config.Epoch != 0 { + return utils.ErrUnauthorized + } + } + } + } + } + + // header must contain validator info following double validation design + // start checking from epoch 2nd. + if header.Number.Uint64() > c.config.Epoch && fullVerify { + validator, err := c.RecoverValidator(header) + if err != nil { + return err + } + + // verify validator + assignedValidator, err := c.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 (c *XDPoS_v1) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) { + epoch := c.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{}, fmt.Errorf("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 (c *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 := c.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + if number%c.config.Epoch != 0 { + c.lock.RLock() + + // Gather all the proposals that make sense voting on + addresses := make([]common.Address, 0, len(c.proposals)) + for address, authorize := range c.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 c.proposals[header.Coinbase] { + copy(header.Nonce[:], utils.NonceAuthVote) + } else { + copy(header.Nonce[:], utils.NonceDropVote) + } + } + c.lock.RUnlock() + } + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + // Set the correct difficulty + header.Difficulty = c.calcDifficulty(chain, parent, c.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 >= c.config.Epoch && number%c.config.Epoch == 0 { + if c.HookPenalty != nil || c.HookPenaltyTIPSigning != nil { + var penMasternodes []common.Address + var err error + if chain.Config().IsTIPSigning(header.Number) { + penMasternodes, err = c.HookPenaltyTIPSigning(chain, header, masternodes) + } else { + penMasternodes, err = c.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)*c.config.Epoch { + masternodes = removePenaltiesFromBlock(chain, masternodes, number-uint64(i)*c.config.Epoch) + } + } + for _, masternode := range masternodes { + header.Extra = append(header.Extra, masternode[:]...) + } + if c.HookValidator != nil { + validators, err := c.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 = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period)) + if header.Time.Int64() < time.Now().Unix() { + header.Time = big.NewInt(time.Now().Unix()) + } + return nil +} + +func (c *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()) + // get snapshot + snap, err := c.snapshot(chain, number, header.Hash(), nil) + 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()) + } + c.recents.Add(snap.Hash, snap) + log.Info("New set of masternodes has been updated to snapshot", "number", snap.Number, "hash", snap.Hash, "new masternodes", nm) + return nil +} + +// Finalize implements consensus.Engine, ensuring no uncles are set, nor block +// rewards given, and returns the final block. +func (c *XDPoS_v1) Finalize(chain consensus.ChainReader, header *types.Header, state *state.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 c.HookReward != nil && number%rCheckpoint == 0 { + err, rewards := c.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 = ioutil.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, txs, nil, receipts), nil +} + +// Authorize injects a private key into the consensus engine to mint new blocks +// with. +func (c *XDPoS_v1) Authorize(signer common.Address, signFn clique.SignerFn) { + c.lock.Lock() + defer c.lock.Unlock() + + c.signer = signer + c.signFn = signFn +} + +// Seal implements consensus.Engine, attempting to create a sealed block using +// the local signing credentials. +func (c *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 c.config.Period == 0 && len(block.Transactions()) == 0 && number%c.config.Epoch != 0 { + return nil, utils.ErrWaitTransactions + } + // Don't hold the signer fields for the entire sealing procedure + c.lock.RLock() + signer, signFn := c.signer, c.signFn + c.lock.RUnlock() + + // Bail out if we're unauthorized to sign a block + snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return nil, err + } + masternodes := c.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%c.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}, sigHash(header).Bytes()) + if err != nil { + return nil, err + } + copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash) + m2, err := c.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 (c *XDPoS_v1) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { + return c.calcDifficulty(chain, parent, c.signer) +} + +func (c *XDPoS_v1) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int { + len, preIndex, curIndex, _, err := c.YourTurn(chain, parent, signer) + if err != nil { + return big.NewInt(int64(len + curIndex - preIndex)) + } + return big.NewInt(int64(len - utils.Hop(len, preIndex, curIndex))) +} + +func (c *XDPoS_v1) RecoverSigner(header *types.Header) (common.Address, error) { + return ecrecover(header, c.signatures) +} + +func (c *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error) { + // If the signature's already cached, return that + hash := header.Hash() + if address, known := c.validatorSignatures.Get(hash); known { + return address.(common.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(sigHash(header).Bytes(), header.Validator) + if err != nil { + return common.Address{}, err + } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + + c.validatorSignatures.Add(hash, signer) + return signer, nil +} + +// Get master nodes over extra data of previous checkpoint block. +func (c *XDPoS_v1) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address { + if preCheckpointHeader == nil { + log.Info("Previous checkpoint's header is empty", "block number", n, "epoch", e) + return []common.Address{} + } + masternodes := make([]common.Address, (len(preCheckpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength) + for i := 0; i < len(masternodes); i++ { + copy(masternodes[i][:], preCheckpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:]) + } + + return masternodes +} + +func (c *XDPoS_v1) CacheData(header *types.Header, txs []*types.Transaction, receipts []*types.Receipt) []*types.Transaction { + signTxs := []*types.Transaction{} + for _, tx := range txs { + if tx.IsSigningTransaction() { + var b uint + 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().String(), "number", header.Number, "len(txs)", len(signTxs)) + c.BlockSigners.Add(header.Hash(), signTxs) + + return signTxs +} + +func (c *XDPoS_v1) CacheSigner(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.String(), "len(txs)", len(signTxs)) + c.BlockSigners.Add(hash, signTxs) + return signTxs +} + +func (c *XDPoS_v1) GetDb() ethdb.Database { + return c.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 +} + +// Get masternodes address from checkpoint Header. +func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address { + masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength) + for i := 0; i < len(masternodes); i++ { + copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:]) + } + return masternodes +} + +// Get m2 list from checkpoint block. +func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) { + if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 { + return nil, errors.New("This block is not checkpoint block epoc.") + } + // Get signers from this block. + masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader) + validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators) + m1m2, _, err := utils.GetM1M2(masternodes, validators, currentHeader, config) + if err != nil { + return map[common.Address]common.Address{}, err + } + return m1m2, nil +} + +func (c *XDPoS_v1) CheckMNTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) bool { + masternodes := c.GetMasternodes(chain, parent) + + if common.IsTestnet { + // Only three mns hard code for XDC testnet. + masternodes = []common.Address{ + common.HexToAddress("0x3Ea0A3555f9B1dE983572BfF6444aeb1899eC58C"), + common.HexToAddress("0x4F7900282F3d371d585ab1361205B0940aB1789C"), + common.HexToAddress("0x942a5885A8844Ee5587C8AC5e371Fc39FFE61896"), + } + } + + snap, err := c.GetSnapshot(chain, parent) + if err != nil { + log.Warn("Failed when trying to commit new work", "err", err) + return false + } + if len(masternodes) == 0 { + return false + } + // masternode[0] has chance to create block 1 + preIndex := -1 + if parent.Number.Uint64() != 0 { + pre, err := whoIsCreator(snap, parent) + if err != nil { + return false + } + preIndex = position(masternodes, pre) + } + curIndex := position(masternodes, signer) + return ((preIndex)%len(masternodes) == curIndex) +} + +func (c *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 := c.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) *XDPoS_v1 { + var fakeEngine *XDPoS_v1 + // Set any missing consensus parameters to their defaults + conf := params.TestXDPoSMockChainConfig.XDPoS + + // Allocate the snapshot caches and create the engine + BlockSigners, _ := lru.New(utils.BlockSignersCacheLimit) + recents, _ := lru.NewARC(utils.InmemorySnapshots) + signatures, _ := lru.NewARC(utils.InmemorySnapshots) + validatorSignatures, _ := lru.NewARC(utils.InmemorySnapshots) + verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots) + fakeEngine = &XDPoS_v1{ + config: conf, + db: db, + BlockSigners: BlockSigners, + recents: recents, + signatures: signatures, + verifiedHeaders: verifiedHeaders, + validatorSignatures: validatorSignatures, + proposals: make(map[common.Address]bool), + } + return fakeEngine +} diff --git a/consensus/XDPoS/snapshot.go b/consensus/XDPoS/engines/engine_v1/snapshot.go similarity index 80% rename from consensus/XDPoS/snapshot.go rename to consensus/XDPoS/engines/engine_v1/snapshot.go index 8642f86b52..3ad8b22189 100644 --- a/consensus/XDPoS/snapshot.go +++ b/consensus/XDPoS/engines/engine_v1/snapshot.go @@ -1,25 +1,11 @@ -// Copyright (c) 2018 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 +package engine_v1 import ( "bytes" "encoding/json" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/clique" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/ethdb" @@ -44,7 +30,7 @@ import ( //} // Snapshot is the state of the authorization voting at a given point in time. -type Snapshot struct { +type SnapshotV1 struct { config *params.XDPoSConfig // Consensus engine parameters to fine tune behavior sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover @@ -59,8 +45,8 @@ 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.XDPoSConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot { - snap := &Snapshot{ +func newSnapshot(config *params.XDPoSConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *SnapshotV1 { + snap := &SnapshotV1{ config: config, sigcache: sigcache, Number: number, @@ -76,12 +62,12 @@ func newSnapshot(config *params.XDPoSConfig, sigcache *lru.ARCCache, number uint } // loadSnapshot loads an existing snapshot from the database. -func loadSnapshot(config *params.XDPoSConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) { +func loadSnapshot(config *params.XDPoSConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*SnapshotV1, error) { blob, err := db.Get(append([]byte("XDPoS-"), hash[:]...)) if err != nil { return nil, err } - snap := new(Snapshot) + snap := new(SnapshotV1) if err := json.Unmarshal(blob, snap); err != nil { return nil, err } @@ -91,8 +77,8 @@ func loadSnapshot(config *params.XDPoSConfig, sigcache *lru.ARCCache, db ethdb.D return snap, nil } -// store inserts the snapshot into the database. -func (s *Snapshot) store(db ethdb.Database) error { +// store inserts the SnapshotV1 into the database. +func (s *SnapshotV1) store(db ethdb.Database) error { blob, err := json.Marshal(s) if err != nil { return err @@ -100,9 +86,9 @@ func (s *Snapshot) store(db ethdb.Database) error { return db.Put(append([]byte("XDPoS-"), s.Hash[:]...), blob) } -// copy creates a deep copy of the snapshot, though not the individual votes. -func (s *Snapshot) copy() *Snapshot { - cpy := &Snapshot{ +// copy creates a deep copy of the SnapshotV1, though not the individual votes. +func (s *SnapshotV1) copy() *SnapshotV1 { + cpy := &SnapshotV1{ config: s.config, sigcache: s.sigcache, Number: s.Number, @@ -128,13 +114,13 @@ func (s *Snapshot) copy() *Snapshot { // validVote returns whether it makes sense to cast the specified vote in the // given snapshot context (e.g. don't try to add an already authorized signer). -func (s *Snapshot) validVote(address common.Address, authorize bool) bool { +func (s *SnapshotV1) validVote(address common.Address, authorize bool) bool { _, signer := s.Signers[address] return (signer && !authorize) || (!signer && authorize) } // cast adds a new vote into the tally. -func (s *Snapshot) cast(address common.Address, authorize bool) bool { +func (s *SnapshotV1) cast(address common.Address, authorize bool) bool { // Ensure the vote is meaningful if !s.validVote(address, authorize) { return false @@ -150,7 +136,7 @@ func (s *Snapshot) cast(address common.Address, authorize bool) bool { } // uncast removes a previously cast vote from the tally. -func (s *Snapshot) uncast(address common.Address, authorize bool) bool { +func (s *SnapshotV1) uncast(address common.Address, authorize bool) bool { // If there's no tally, it's a dangling vote, just drop tally, ok := s.Tally[address] if !ok { @@ -170,9 +156,9 @@ func (s *Snapshot) uncast(address common.Address, authorize bool) bool { return true } -// apply creates a new authorization snapshot by applying the given headers to +// apply creates a new authorization SnapshotV1 by applying the given headers to // the original one. -func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { +func (s *SnapshotV1) apply(headers []*types.Header) (*SnapshotV1, error) { // Allow passing in no headers for cleaner code if len(headers) == 0 { return s, nil @@ -180,13 +166,13 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { // Sanity check that the headers can be applied for i := 0; i < len(headers)-1; i++ { if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 { - return nil, errInvalidVotingChain + return nil, utils.ErrInvalidVotingChain } } if headers[0].Number.Uint64() != s.Number+1 { - return nil, errInvalidVotingChain + return nil, utils.ErrInvalidVotingChain } - // Iterate through the headers and create a new snapshot + // Iterate through the headers and create a new SnapshotV1 snap := s.copy() for _, header := range headers { @@ -230,12 +216,12 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { // Tally up the new vote from the signer var authorize bool switch { - case bytes.Equal(header.Nonce[:], nonceAuthVote): + case bytes.Equal(header.Nonce[:], utils.NonceAuthVote): authorize = true - case bytes.Equal(header.Nonce[:], nonceDropVote): + case bytes.Equal(header.Nonce[:], utils.NonceDropVote): authorize = false default: - return nil, errInvalidVote + return nil, utils.ErrInvalidVote } if snap.cast(header.Coinbase, authorize) { snap.Votes = append(snap.Votes, &clique.Vote{ @@ -286,7 +272,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { } // signers retrieves the list of authorized signers in ascending order. -func (s *Snapshot) GetSigners() []common.Address { +func (s *SnapshotV1) GetSigners() []common.Address { signers := make([]common.Address, 0, len(s.Signers)) for signer := range s.Signers { signers = append(signers, signer) @@ -300,12 +286,3 @@ func (s *Snapshot) GetSigners() []common.Address { } return signers } - -// inturn returns if a signer at a given block height is in-turn or not. -func (s *Snapshot) inturn(number uint64, signer common.Address) bool { - signers, offset := s.GetSigners(), 0 - for offset < len(signers) && signers[offset] != signer { - offset++ - } - return (number % uint64(len(signers))) == uint64(offset) -} diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go new file mode 100644 index 0000000000..da3b04244d --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -0,0 +1 @@ +package engine_v2 diff --git a/consensus/XDPoS/engines/engine_v2/snapshot.go b/consensus/XDPoS/engines/engine_v2/snapshot.go new file mode 100644 index 0000000000..da3b04244d --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/snapshot.go @@ -0,0 +1 @@ +package engine_v2 diff --git a/consensus/XDPoS/utils/constants.go b/consensus/XDPoS/utils/constants.go new file mode 100644 index 0000000000..f2c7490ccc --- /dev/null +++ b/consensus/XDPoS/utils/constants.go @@ -0,0 +1,25 @@ +package utils + +import ( + "github.com/XinFinOrg/XDPoSChain/common/hexutil" + "github.com/XinFinOrg/XDPoSChain/core/types" +) + +// XDPoS delegated-proof-of-stake protocol constants. +var ( + EpochLength = uint64(900) // 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 = 65 // 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. +) + +const ( + InmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + BlockSignersCacheLimit = 9000 + M2ByteLength = 4 +) diff --git a/consensus/XDPoS/utils/errors.go b/consensus/XDPoS/utils/errors.go new file mode 100644 index 0000000000..5d7faf1a81 --- /dev/null +++ b/consensus/XDPoS/utils/errors.go @@ -0,0 +1,74 @@ +package utils + +import "errors" + +// Various error messages to mark blocks invalid. These should be private to +// prevent engine specific errors from being referenced in the remainder of the +// codebase, inherently breaking if the engine is swapped out. Please put common +// error types into the consensus package. +var ( + // errUnknownBlock is returned when the list of signers is requested for a block + // that is not part of the local blockchain. + ErrUnknownBlock = errors.New("unknown block") + + // errInvalidCheckpointBeneficiary is returned if a checkpoint/epoch transition + // block has a beneficiary set to non-zeroes. + ErrInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero") + + // errInvalidVote is returned if a nonce value is something else that the two + // allowed constants of 0x00..0 or 0xff..f. + ErrInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f") + + // errInvalidCheckpointVote is returned if a checkpoint/epoch transition block + // has a vote nonce set to non-zeroes. + ErrInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero") + + // errMissingVanity is returned if a block's extra-data section is shorter than + // 32 bytes, which is required to store the signer vanity. + ErrMissingVanity = errors.New("extra-data 32 byte vanity prefix missing") + + // errMissingSignature is returned if a block's extra-data section doesn't seem + // to contain a 65 byte secp256k1 signature. + ErrMissingSignature = errors.New("extra-data 65 byte suffix signature missing") + + // errExtraSigners is returned if non-checkpoint block contain signer data in + // their extra-data fields. + ErrExtraSigners = errors.New("non-checkpoint block contains extra signer list") + + // errInvalidCheckpointSigners is returned if a checkpoint block contains an + // invalid list of signers (i.e. non divisible by 20 bytes, or not the correct + // ones). + ErrInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block") + + ErrInvalidCheckpointPenalties = errors.New("invalid penalty list on checkpoint block") + + // errInvalidMixDigest is returned if a block's mix digest is non-zero. + ErrInvalidMixDigest = errors.New("non-zero mix digest") + + // errInvalidUncleHash is returned if a block contains an non-empty uncle list. + ErrInvalidUncleHash = errors.New("non empty uncle hash") + + // errInvalidDifficulty is returned if the difficulty of a block is not either + // of 1 or 2, or if the value does not match the turn of the signer. + ErrInvalidDifficulty = errors.New("invalid difficulty") + + // ErrInvalidTimestamp is returned if the timestamp of a block is lower than + // the previous block's timestamp + the minimum block period. + ErrInvalidTimestamp = errors.New("invalid timestamp") + + // errInvalidVotingChain is returned if an authorization list is attempted to + // be modified via out-of-range or non-contiguous headers. + ErrInvalidVotingChain = errors.New("invalid voting chain") + + // errUnauthorized is returned if a header is signed by a non-authorized entity. + ErrUnauthorized = errors.New("unauthorized") + + ErrFailedDoubleValidation = errors.New("wrong pair of creator-validator in double validation") + + // errWaitTransactions is returned if an empty block is attempted to be sealed + // on an instant chain (0 second period). It's important to refuse these as the + // block reward is zero, so an empty block just bloats the chain... fast. + ErrWaitTransactions = errors.New("waiting for transactions") + + ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block") +) diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go new file mode 100644 index 0000000000..754194c728 --- /dev/null +++ b/consensus/XDPoS/utils/types.go @@ -0,0 +1,58 @@ +package utils + +import ( + "math/big" + "time" + + "github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate" + "github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/clique" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "gopkg.in/karalabe/cookiejar.v2/collections/prque" +) + +type Masternode struct { + Address common.Address + Stake *big.Int +} + +type TradingService interface { + GetTradingStateRoot(block *types.Block, author common.Address) (common.Hash, error) + GetTradingState(block *types.Block, author common.Address) (*tradingstate.TradingStateDB, error) + HasTradingState(block *types.Block, author common.Address) bool + GetStateCache() tradingstate.Database + GetTriegc() *prque.Prque + ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, XDCXstatedb *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) + UpdateMediumPriceBeforeEpoch(epochNumber uint64, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB) error + IsSDKNode() bool + SyncDataToSDKNode(takerOrder *tradingstate.OrderItem, txHash common.Hash, txMatchTime time.Time, statedb *state.StateDB, trades []map[string]string, rejectedOrders []*tradingstate.OrderItem, dirtyOrderCount *uint64) error + RollbackReorgTxMatch(txhash common.Hash) error + GetTokenDecimal(chain consensus.ChainContext, statedb *state.StateDB, tokenAddr common.Address) (*big.Int, error) +} + +type LendingService interface { + GetLendingStateRoot(block *types.Block, author common.Address) (common.Hash, error) + GetLendingState(block *types.Block, author common.Address) (*lendingstate.LendingStateDB, error) + HasLendingState(block *types.Block, author common.Address) bool + GetStateCache() lendingstate.Database + GetTriegc() *prque.Prque + ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, lendingStateDB *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB, lendingOrderBook common.Hash, order *lendingstate.LendingItem) ([]*lendingstate.LendingTrade, []*lendingstate.LendingItem, error) + GetCollateralPrices(header *types.Header, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, collateralToken common.Address, lendingToken common.Address) (*big.Int, *big.Int, error) + GetMediumTradePriceBeforeEpoch(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, baseToken common.Address, quoteToken common.Address) (*big.Int, error) + ProcessLiquidationData(header *types.Header, chain consensus.ChainContext, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, lendingState *lendingstate.LendingStateDB) (updatedTrades map[common.Hash]*lendingstate.LendingTrade, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades []*lendingstate.LendingTrade, err error) + SyncDataToSDKNode(chain consensus.ChainContext, state *state.StateDB, block *types.Block, takerOrderInTx *lendingstate.LendingItem, txHash common.Hash, txMatchTime time.Time, trades []*lendingstate.LendingTrade, rejectedOrders []*lendingstate.LendingItem, dirtyOrderCount *uint64) error + UpdateLiquidatedTrade(blockTime uint64, result lendingstate.FinalizedResult, trades map[common.Hash]*lendingstate.LendingTrade) error + RollbackLendingData(txhash common.Hash) error +} + +type PublicApiSnapshot struct { + Number uint64 `json:"number"` // Block number where the snapshot was created + Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + Signers map[common.Address]struct{} `json:"signers"` // Set of authorized signers at this moment + Recents map[uint64]common.Address `json:"recents"` // Set of recent signers for spam protections + Votes []*clique.Vote `json:"votes"` // List of votes cast in chronological order + Tally map[common.Address]clique.Tally `json:"tally"` // Current vote tally to avoid recalculating +} diff --git a/consensus/XDPoS/utils/utils.go b/consensus/XDPoS/utils/utils.go new file mode 100644 index 0000000000..a117984509 --- /dev/null +++ b/consensus/XDPoS/utils/utils.go @@ -0,0 +1,111 @@ +package utils + +import ( + "bytes" + "errors" + "reflect" + "sort" + "strconv" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/params" +) + +func Position(list []common.Address, x common.Address) int { + for i, item := range list { + if item == x { + return i + } + } + return -1 +} + +func Hop(len, pre, cur int) int { + switch { + case pre < cur: + return cur - (pre + 1) + case pre > cur: + return (len - pre) + (cur - 1) + default: + return len - 1 + } +} + +// Extract validators from byte array. +func ExtractValidatorsFromBytes(byteValidators []byte) []int64 { + lenValidator := len(byteValidators) / M2ByteLength + var validators []int64 + for i := 0; i < lenValidator; i++ { + trimByte := bytes.Trim(byteValidators[i*M2ByteLength:(i+1)*M2ByteLength], "\x00") + intNumber, err := strconv.Atoi(string(trimByte)) + if err != nil { + log.Error("Can not convert string to integer", "error", err) + return []int64{} + } + validators = append(validators, int64(intNumber)) + } + + return validators +} + +// Get masternodes address from checkpoint Header. +func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address { + masternodes := make([]common.Address, (len(checkpointHeader.Extra)-ExtraVanity-ExtraSeal)/common.AddressLength) + for i := 0; i < len(masternodes); i++ { + copy(masternodes[i][:], checkpointHeader.Extra[ExtraVanity+i*common.AddressLength:]) + } + return masternodes +} + +// Get m2 list from checkpoint block. +func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) { + if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 { + return nil, errors.New("This block is not checkpoint block epoc.") + } + // Get signers from this block. + masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader) + validators := ExtractValidatorsFromBytes(checkpointHeader.Validators) + m1m2, _, err := GetM1M2(masternodes, validators, currentHeader, config) + if err != nil { + return map[common.Address]common.Address{}, err + } + return m1m2, nil +} + +func GetM1M2(masternodes []common.Address, validators []int64, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, uint64, error) { + m1m2 := map[common.Address]common.Address{} + maxMNs := len(masternodes) + moveM2 := uint64(0) + if len(validators) < maxMNs { + return nil, moveM2, errors.New("len(m2) is less than len(m1)") + } + if maxMNs > 0 { + isForked := config.IsTIPRandomize(currentHeader.Number) + if isForked { + moveM2 = ((currentHeader.Number.Uint64() % config.XDPoS.Epoch) / uint64(maxMNs)) % uint64(maxMNs) + } + for i, m1 := range masternodes { + m2Index := uint64(validators[i] % int64(maxMNs)) + m2Index = (m2Index + moveM2) % uint64(maxMNs) + m1m2[m1] = masternodes[m2Index] + } + } + return m1m2, moveM2, nil +} + +// compare 2 signers lists +// return true if they are same elements, otherwise return false +func CompareSignersLists(list1 []common.Address, list2 []common.Address) bool { + if len(list1) == 0 && len(list2) == 0 { + return true + } + sort.Slice(list1, func(i, j int) bool { + return list1[i].String() <= list1[j].String() + }) + sort.Slice(list2, func(i, j int) bool { + return list2[i].String() <= list2[j].String() + }) + return reflect.DeepEqual(list1, list2) +} diff --git a/contracts/utils.go b/contracts/utils.go index 7179a03cf1..373e8d0121 100644 --- a/contracts/utils.go +++ b/contracts/utils.go @@ -35,7 +35,9 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/contracts/blocksigner/contract" randomizeContract "github.com/XinFinOrg/XDPoSChain/contracts/randomize/contract" "github.com/XinFinOrg/XDPoSChain/core" @@ -278,7 +280,7 @@ func BuildValidatorFromM2(listM2 []int64) []byte { var validatorBytes []byte for _, numberM2 := range listM2 { // Convert number to byte. - m2Byte := common.LeftPadBytes([]byte(fmt.Sprintf("%d", numberM2)), XDPoS.M2ByteLength) + m2Byte := common.LeftPadBytes([]byte(fmt.Sprintf("%d", numberM2)), utils.M2ByteLength) validatorBytes = append(validatorBytes, m2Byte...) } @@ -292,7 +294,7 @@ func DecodeValidatorsHexData(validatorsStr string) ([]int64, error) { return nil, err } - return XDPoS.ExtractValidatorsFromBytes(validatorsByte), nil + return utils.ExtractValidatorsFromBytes(validatorsByte), nil } // Decrypt randomize from secrets and opening. @@ -350,7 +352,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header } } header = chain.GetHeader(header.ParentHash, prevCheckpoint) - masternodes := XDPoS.GetMasternodesFromCheckpointHeader(header) + masternodes := utils.GetMasternodesFromCheckpointHeader(header) for i := startBlockNumber; i <= endBlockNumber; i++ { if i%common.MergeSignRange == 0 || !chain.Config().IsTIP2019(big.NewInt(int64(i))) { diff --git a/contracts/utils_test.go b/contracts/utils_test.go index d267eda83e..4473502570 100644 --- a/contracts/utils_test.go +++ b/contracts/utils_test.go @@ -27,7 +27,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/contracts/blocksigner" "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/types" @@ -191,7 +191,7 @@ func TestGenM2FromRandomize(t *testing.T) { func TestBuildValidatorFromM2(t *testing.T) { a := []int64{84, 58, 27, 96, 127, 60, 136, 20, 121, 31, 87, 85, 40, 120, 149, 109, 141, 145, 11, 110, 147, 35, 76, 46, 34, 108, 72, 103, 102, 12, 23, 47, 70, 86, 125, 112, 128, 13, 130, 98, 126, 62, 132, 111, 134, 6, 106, 67, 24, 91, 101, 50, 94, 43, 77, 73, 129, 71, 51, 10, 92, 29, 80, 95, 33, 100, 124, 75, 38, 133, 79, 83, 61, 36, 122, 99, 16, 28, 18, 116, 140, 97, 119, 82, 148, 48, 56, 32, 93, 107, 69, 68, 123, 81, 22, 137, 25, 115, 44, 8, 42, 131, 143, 17, 55, 89, 9, 15, 19, 59, 146, 54, 5, 30, 41, 144, 117, 1, 104, 49, 105, 45, 88, 78, 74, 135, 0, 21, 57, 3, 66, 52, 63, 138, 4, 114, 37, 118, 14, 2, 26, 7, 65, 139, 39, 64, 90, 142, 53, 113} b := BuildValidatorFromM2(a) - c := XDPoS.ExtractValidatorsFromBytes(b) + c := utils.ExtractValidatorsFromBytes(b) if !isArrayEqual([][]int64{a}, [][]int64{c}) { t.Errorf("Fail to get m2 result %v", b) } diff --git a/core/blockchain.go b/core/blockchain.go index cf9bb23a28..16a03ae5dd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -37,6 +37,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common/mclock" "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" @@ -877,8 +878,8 @@ func (bc *BlockChain) SaveData() { var lendingTriedb *trie.Database engine, _ := bc.Engine().(*XDPoS.XDPoS) triedb := bc.stateCache.TrieDB() - var tradingService XDPoS.TradingService - var lendingService XDPoS.LendingService + var tradingService utils.TradingService + var lendingService utils.LendingService if bc.Config().IsTIPXDCX(bc.CurrentBlock().Number()) && bc.chainConfig.XDPoS != nil && bc.CurrentBlock().NumberU64() > bc.chainConfig.XDPoS.Epoch && engine != nil { tradingService = engine.GetXDCXService() if tradingService != nil && tradingService.GetStateCache() != nil { @@ -1201,9 +1202,9 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types. } engine, _ := bc.Engine().(*XDPoS.XDPoS) var tradingTrieDb *trie.Database - var tradingService XDPoS.TradingService + var tradingService utils.TradingService var lendingTrieDb *trie.Database - var lendingService XDPoS.LendingService + var lendingService utils.LendingService if bc.Config().IsTIPXDCX(block.Number()) && bc.chainConfig.XDPoS != nil && block.NumberU64() > bc.chainConfig.XDPoS.Epoch && engine != nil { tradingService = engine.GetXDCXService() if tradingService != nil { @@ -1541,8 +1542,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty // clear the previous dry-run cache var tradingState *tradingstate.TradingStateDB var lendingState *lendingstate.LendingStateDB - var tradingService XDPoS.TradingService - var lendingService XDPoS.LendingService + var tradingService utils.TradingService + var lendingService utils.LendingService isSDKNode := false if bc.Config().IsTIPXDCX(block.Number()) && bc.chainConfig.XDPoS != nil && engine != nil && block.NumberU64() > bc.chainConfig.XDPoS.Epoch { tradingService = engine.GetXDCXService() @@ -1827,8 +1828,8 @@ func (bc *BlockChain) getResultBlock(block *types.Block, verifiedM2 bool) (*Resu var tradingState *tradingstate.TradingStateDB var lendingState *lendingstate.LendingStateDB - var tradingService XDPoS.TradingService - var lendingService XDPoS.LendingService + var tradingService utils.TradingService + var lendingService utils.LendingService isSDKNode := false if bc.Config().IsTIPXDCX(block.Number()) && bc.chainConfig.XDPoS != nil && engine != nil && block.NumberU64() > bc.chainConfig.XDPoS.Epoch { tradingService = engine.GetXDCXService() @@ -2481,7 +2482,7 @@ func (bc *BlockChain) UpdateM1() error { return err } - var ms []XDPoS.Masternode + var ms []utils.Masternode for _, candidate := range candidates { v, err := validator.GetCandidateCap(opts, candidate) if err != nil { @@ -2489,7 +2490,7 @@ func (bc *BlockChain) UpdateM1() error { } //TODO: smart contract shouldn't return "0x0000000000000000000000000000000000000000" if candidate.String() != "xdc0000000000000000000000000000000000000000" { - ms = append(ms, XDPoS.Masternode{Address: candidate, Stake: v}) + ms = append(ms, utils.Masternode{Address: candidate, Stake: v}) } } if len(ms) == 0 { diff --git a/eth/backend.go b/eth/backend.go index e455f5720b..02a0fc0b37 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -41,6 +41,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/contracts" contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract" @@ -172,10 +173,10 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS ) if eth.chainConfig.XDPoS != nil { c := eth.engine.(*XDPoS.XDPoS) - c.GetXDCXService = func() XDPoS.TradingService { + c.GetXDCXService = func() utils.TradingService { return eth.XDCX } - c.GetLendingService = func() XDPoS.LendingService { + c.GetLendingService = func() utils.LendingService { return eth.Lending } } @@ -471,7 +472,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS opts := new(bind.CallOpts) var ( candidateAddresses []common.Address - candidates []XDPoS.Masternode + candidates []utils.Masternode ) stateDB, err := eth.blockchain.StateAt(eth.blockchain.GetBlockByHash(block).Root()) @@ -486,7 +487,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS return nil, err } if address.String() != "0x0000000000000000000000000000000000000000" { - candidates = append(candidates, XDPoS.Masternode{Address: address, Stake: v}) + candidates = append(candidates, utils.Masternode{Address: address, Stake: v}) } } // sort candidates by stake descending @@ -565,7 +566,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS return err } if !bytes.Equal(header.Validators, validators) { - return XDPoS.ErrInvalidCheckpointValidators + return utils.ErrInvalidCheckpointValidators } } return nil @@ -581,15 +582,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS // not genesis block header = parentHeader } - snap, err := c.GetSnapshot(eth.blockchain, header) - if err != nil { - log.Error("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 + return c.IsAuthorisedAddress(header, eth.blockchain, address) } } @@ -756,11 +749,9 @@ func (s *Ethereum) ValidateMasternode() (bool, error) { if s.chainConfig.XDPoS != nil { //check if miner's wallet is in set of validators c := s.engine.(*XDPoS.XDPoS) - snap, err := c.GetSnapshot(s.blockchain, s.blockchain.CurrentHeader()) - if err != nil { - return false, fmt.Errorf("Can't verify masternode permission: %v", err) - } - if _, authorized := snap.Signers[eb]; !authorized { + + authorized := c.IsAuthorisedAddress(s.blockchain.CurrentHeader(), s.blockchain, eb) + if !authorized { //This miner doesn't belong to set of validators return false, nil } diff --git a/go.sum b/go.sum index 59d71d648f..0394f8a511 100644 --- a/go.sum +++ b/go.sum @@ -210,6 +210,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -250,6 +251,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= @@ -334,6 +336,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9a415ca4de..f337ee5228 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -37,6 +37,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/ethash" contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract" "github.com/XinFinOrg/XDPoSChain/core" @@ -713,7 +714,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd checkpointNumber rpc.BlockNumber epochNumber rpc.EpochNumber // if epoch == "latest", print the latest epoch number to epochNumber masternodes, penaltyList []common.Address - candidates []XDPoS.Masternode + candidates []utils.Masternode penalties []byte err error ) @@ -755,7 +756,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd for _, address := range candidatesAddresses { v := state.GetCandidateCap(statedb, address) if address.String() != "0x0000000000000000000000000000000000000000" { - candidates = append(candidates, XDPoS.Masternode{Address: address, Stake: v}) + candidates = append(candidates, utils.Masternode{Address: address, Stake: v}) } } } @@ -843,7 +844,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch epochNumber rpc.EpochNumber masternodes []common.Address penaltyList []common.Address - candidates []XDPoS.Masternode + candidates []utils.Masternode penalties []byte err error ) @@ -881,7 +882,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch for _, address := range candidatesAddresses { v := state.GetCandidateCap(statedb, address) if address.String() != "0x0000000000000000000000000000000000000000" { - candidates = append(candidates, XDPoS.Masternode{Address: address, Stake: v}) + candidates = append(candidates, utils.Masternode{Address: address, Stake: v}) } } } @@ -939,7 +940,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch } penaltyList = common.ExtractAddressFromBytes(penalties) - var topCandidates []XDPoS.Masternode + var topCandidates []utils.Masternode if len(candidates) > common.MaxMasternodes { topCandidates = candidates[:common.MaxMasternodes] } else { @@ -984,33 +985,33 @@ func (s *PublicBlockChainAPI) GetPreviousCheckpointFromEpoch(ctx context.Context } // getCandidatesFromSmartContract returns all candidates with their capacities at the current time -func (s *PublicBlockChainAPI) getCandidatesFromSmartContract() ([]XDPoS.Masternode, error) { +func (s *PublicBlockChainAPI) getCandidatesFromSmartContract() ([]utils.Masternode, error) { client, err := s.b.GetIPCClient() if err != nil { - return []XDPoS.Masternode{}, err + return []utils.Masternode{}, err } addr := common.HexToAddress(common.MasternodeVotingSMC) validator, err := contractValidator.NewXDCValidator(addr, client) if err != nil { - return []XDPoS.Masternode{}, err + return []utils.Masternode{}, err } opts := new(bind.CallOpts) candidates, err := validator.GetCandidates(opts) if err != nil { - return []XDPoS.Masternode{}, err + return []utils.Masternode{}, err } - var candidatesWithStakeInfo []XDPoS.Masternode + var candidatesWithStakeInfo []utils.Masternode for _, candidate := range candidates { v, err := validator.GetCandidateCap(opts, candidate) if err != nil { - return []XDPoS.Masternode{}, err + return []utils.Masternode{}, err } if candidate.String() != "0x0000000000000000000000000000000000000000" { - candidatesWithStakeInfo = append(candidatesWithStakeInfo, XDPoS.Masternode{Address: candidate, Stake: v}) + candidatesWithStakeInfo = append(candidatesWithStakeInfo, utils.Masternode{Address: candidate, Stake: v}) } if len(candidatesWithStakeInfo) > 0 { diff --git a/miner/worker.go b/miner/worker.go index 0ea43a9457..114511b4d6 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -34,6 +34,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/contracts" "github.com/XinFinOrg/XDPoSChain/core" @@ -396,12 +397,9 @@ func (self *worker) wait() { if self.config.XDPoS != nil { c := self.engine.(*XDPoS.XDPoS) - snap, err := c.GetSnapshot(self.chain, block.Header()) - if err != nil { - log.Error("Fail to get snapshot for sign tx signer.") - return - } - if _, authorized := snap.Signers[self.coinbase]; !authorized { + + authorized := c.IsAuthorisedAddress(block.Header(), self.chain, self.coinbase) + if !authorized { valid := false masternodes := c.GetMasternodes(self.chain, block.Header()) for _, m := range masternodes { @@ -542,7 +540,7 @@ func (self *worker) commitNewWork() { // you're not allowed to create this block return } - h := XDPoS.Hop(len, preIndex, curIndex) + h := utils.Hop(len, preIndex, curIndex) gap := waitPeriod * int64(h) // Check nearest checkpoint block in hop range. nearest := self.config.XDPoS.Epoch - (parent.Header().Number.Uint64() % self.config.XDPoS.Epoch) diff --git a/params/config.go b/params/config.go index 2523925021..9f218c582b 100644 --- a/params/config.go +++ b/params/config.go @@ -183,6 +183,14 @@ func (c *XDPoSConfig) String() string { return "XDPoS" } +/** +ConsensusVersion will return the consensus version to use for the provided block number. The returned int represent its version +TODO: It's a dummy value for now until the 2.0 consensus engine is fully implemented. +*/ +func BlockConsensusVersion(num *big.Int) int { + return 1 +} + // String implements the fmt.Stringer interface. func (c *ChainConfig) String() string { var engine interface{}