diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index 42d83a68f3..e3fde0f225 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/XDPoS" "github.com/ethereum/go-ethereum/console" validatorContract "github.com/ethereum/go-ethereum/contracts/validator/contract" "github.com/ethereum/go-ethereum/core" @@ -371,7 +371,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { utils.Fatalf("Can't get list of masternode candidates: %v", err) } - var ms []clique.Masternode + var ms []XDPoS.Masternode for _, candidate := range candidates { v, err := validator.GetCandidateCap(opts, candidate) if err != nil { @@ -379,7 +379,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { } //TODO: smart contract shouldn't return "0x0000000000000000000000000000000000000000" if candidate.String() != "0x0000000000000000000000000000000000000000" { - ms = append(ms, clique.Masternode{Address: candidate, Stake: v.String()}) + ms = append(ms, XDPoS.Masternode{Address: candidate, Stake: v.String()}) } } //// order by cap diff --git a/cmd/puppeth/module_node.go b/cmd/puppeth/module_node.go index 43c20c0361..3538d5e873 100644 --- a/cmd/puppeth/module_node.go +++ b/cmd/puppeth/module_node.go @@ -184,7 +184,7 @@ func (info *nodeInfos) Report() map[string]string { report["Miner account"] = info.etherbase } if info.keyJSON != "" { - // Clique proof-of-authority signer + // Clique XDPos var key struct { Address string `json:"address"` } diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 00ce4eafdf..0352a3afcf 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -58,10 +58,10 @@ func (w *wizard) makeGenesis() { } // Figure out which consensus engine to choose fmt.Println() - fmt.Println("Which consensus engine to use? (default = clique)") + fmt.Println("Which consensus engine to use? (default = XDPos)") fmt.Println(" 1. Ethash - proof-of-work") - fmt.Println(" 2. Clique - proof-of-authority") - fmt.Println(" 3. Xdpos - proof-of-stake-voting") + fmt.Println(" 2. XDPos - proof-of-authority") + fmt.Println(" 3. XDPos - proof-of-stake-voting") choice := w.read() switch { @@ -70,22 +70,17 @@ func (w *wizard) makeGenesis() { genesis.Config.Ethash = new(params.EthashConfig) genesis.ExtraData = make([]byte, 32) - case choice == "" || choice == "2": + case choice == "2": // In the case of clique, configure the consensus parameters genesis.Difficulty = big.NewInt(1) genesis.Config.Clique = ¶ms.CliqueConfig{ Period: 15, Epoch: 30000, - Reward: 0, } fmt.Println() fmt.Println("How many seconds should blocks take? (default = 15)") genesis.Config.Clique.Period = uint64(w.readDefaultInt(15)) - fmt.Println() - fmt.Println("How many Ethers should be rewarded to signer? (default = 0)") - genesis.Config.Clique.Reward = uint64(w.readDefaultInt(0)) - // We also need the initial list of signers fmt.Println() fmt.Println("Which accounts are allowed to seal? (mandatory at least one)") @@ -113,29 +108,20 @@ func (w *wizard) makeGenesis() { copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:]) } - fmt.Println() - fmt.Println("How many blocks per checkpoint? (default = 990)") - genesis.Config.Clique.Epoch = uint64(w.readDefaultInt(990)) - genesis.Config.Clique.RewardCheckpoint = genesis.Config.Clique.Epoch - - fmt.Println() - fmt.Println("How many blocks before checkpoint need to prepare new set of masternodes? (default = 50)") - genesis.Config.Clique.Gap = uint64(w.readDefaultInt(50)) - - case choice == "3": + case choice == "" || choice == "3": genesis.Difficulty = big.NewInt(1) - genesis.Config.Clique = ¶ms.CliqueConfig{ + genesis.Config.XDPoS = ¶ms.XDPosConfig{ Period: 15, Epoch: 30000, Reward: 0, } fmt.Println() fmt.Println("How many seconds should blocks take? (default = 2)") - genesis.Config.Clique.Period = uint64(w.readDefaultInt(2)) + genesis.Config.XDPoS.Period = uint64(w.readDefaultInt(2)) fmt.Println() fmt.Println("How many Ethers should be rewarded to masternode? (default = 10)") - genesis.Config.Clique.Reward = uint64(w.readDefaultInt(10)) + genesis.Config.XDPoS.Reward = uint64(w.readDefaultInt(10)) fmt.Println() fmt.Println("Who own the first masternodes? (mandatory)") @@ -175,11 +161,11 @@ func (w *wizard) makeGenesis() { fmt.Println() fmt.Println("How many blocks per epoch? (default = 990)") epochNumber := w.readDefaultInt(990) - genesis.Config.Clique.RewardCheckpoint = uint64(epochNumber) + genesis.Config.XDPoS.RewardCheckpoint = uint64(epochNumber) fmt.Println() fmt.Println("How many blocks before checkpoint need to prepare new set of masternodes? (default = 50)") - genesis.Config.Clique.Gap = uint64(w.readDefaultInt(50)) + genesis.Config.XDPoS.Gap = uint64(w.readDefaultInt(50)) // Validator Smart Contract Code pKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") diff --git a/cmd/puppeth/wizard_node.go b/cmd/puppeth/wizard_node.go index a60948bc67..d766ab2cf3 100644 --- a/cmd/puppeth/wizard_node.go +++ b/cmd/puppeth/wizard_node.go @@ -118,7 +118,7 @@ func (w *wizard) deployNode(boot bool) { fmt.Printf("What address should the miner user? (default = %s)\n", infos.etherbase) infos.etherbase = w.readDefaultAddress(common.HexToAddress(infos.etherbase)).Hex() } - } else if w.conf.Genesis.Config.Clique != nil { + } else if w.conf.Genesis.Config.XDPoS != nil { // If a previous signer was already set, offer to reuse it if infos.keyJSON != "" { if key, err := keystore.DecryptKey([]byte(infos.keyJSON), infos.keyPass); err != nil { @@ -131,7 +131,7 @@ func (w *wizard) deployNode(boot bool) { } } } - // Clique based signers need a keyfile and unlock password, ask if unavailable + // XDPoS based signers need a keyfile and unlock password, ask if unavailable if infos.keyJSON == "" { fmt.Println() fmt.Println("Please paste the signer's key JSON:") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c00dd9ecfe..f98bef74ac 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/XDPoS" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -1221,8 +1221,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai Fatalf("%v", err) } var engine consensus.Engine - if config.Clique != nil { - engine = clique.New(config.Clique, chainDb) + if config.XDPoS != nil { + engine = XDPoS.New(config.XDPoS, chainDb) } else { engine = ethash.NewFaker() if !ctx.GlobalBool(FakePoWFlag.Name) { diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go new file mode 100644 index 0000000000..fbaaf12531 --- /dev/null +++ b/consensus/XDPoS/XDPoS.go @@ -0,0 +1,1099 @@ +// Copyright (c) 2018 XDCchain +// +// 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 implements the XinFin-DPoS consensus engine. +package XDPoS + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "math/rand" + "strconv" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/hashicorp/golang-lru" + +) + +const ( + inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + M2ByteLength = 4 +) + +type Masternode struct { + Address common.Address + Stake *big.Int +} + +// XDPoS XinFin-DPoS 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 XinFin-DPoS +// 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 + } + // 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 XinFin-DPoS consensus engine proposed to support the +// Ethereum testnet following the Ropsten attacks. +type XDPoS struct { + config *params.XDPoSConfig // Consensus engine configuration parameters + db ethdb.Database // Database to store and retrieve snapshot checkpoints + + 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 + rewards *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 + + HookReward func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) (error, map[string]interface{}) + HookPenalty func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error) + HookValidator func(header *types.Header, signers []common.Address) ([]byte, error) + HookVerifyMNs func(header *types.Header, signers []common.Address) error +} + +// New creates a XDPoS XinFin-DPoS consensus engine with the initial +// signers set to the ones provided by the user. +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 + } + // Allocate the snapshot caches and create the engine + recents, _ := lru.NewARC(inmemorySnapshots) + signatures, _ := lru.NewARC(inmemorySnapshots) + validatorSignatures, _ := lru.NewARC(inmemorySnapshots) + verifiedHeaders, _ := lru.NewARC(inmemorySnapshots) + rewards, _ := lru.NewARC(inmemorySnapshots) + return &XDPoS{ + config: &conf, + db: db, + recents: recents, + signatures: signatures, + verifiedHeaders: verifiedHeaders, + validatorSignatures: validatorSignatures, + rewards: rewards, + 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) Author(header *types.Header) (common.Address, error) { + return ecrecover(header, c.signatures) +} + +// 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 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 + } + // Retrieve the snapshot needed to verify this header and cache it + snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + // If the block is a checkpoint block, verify the signer list + if number%c.config.Epoch == 0 { + penPenalties := []common.Address{} + if c.HookPenalty != nil { + 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 := snap.GetSigners() + 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) + } + } + byteMasterNodes := common.ExtractAddressToBytes(signers) + extraSuffix := len(header.Extra) - extraSeal + if !bytes.Equal(header.Extra[extraVanity:extraSuffix], byteMasterNodes) { + return errInvalidCheckpointSigners + } + if c.HookVerifyMNs != nil { + err := c.HookVerifyMNs(header, signers) + if err != nil { + return err + } + } + } + // All basic checks passed, verify the seal and return + return c.verifySeal(chain, header, parents, fullVerify) +} + +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 for XDC testnet. + masternodes = masternodes[:3] + } + 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) + 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 > 0 && number%c.config.Epoch == 0 { + if c.HookPenalty != nil { + 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, "block 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, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + // set block reward + number := header.Number.Uint64() + rCheckpoint := chain.Config().XDPoS.RewardCheckpoint + + if c.HookReward != nil && number%rCheckpoint == 0 { + err, rewardResults := c.HookReward(chain, state, header) + if err != nil { + return nil, err + } + c.rewards.Add(header.Hash(), rewardResults) + } + + // 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) + if c.config.Period == 0 && len(block.Transactions()) == 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("Length of MasterNodes", "len(masternodes)", len(masternodes), "number", number, "limit", limit, "seen", seen, "recent", recent.String(), "snap.Recents", snap.Recents) + log.Info("Signed recently, must wait for others") + <-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) + + 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 { + 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))) +} + +// 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 { + return []rpc.API{{ + Namespace: "XDPoS", + Version: "1.0", + Service: &API{chain: chain, XDPoS: c}, + Public: false, + }} +} + +func (c *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) { + return ecrecover(header, c.signatures) +} + +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 + } + // 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{} + } + 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 +} + +// 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) (map[common.Address]common.Address, error) { + if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 { + return nil, errors.New("This block is not checkpoint block epoc.") + } + m1m2 := map[common.Address]common.Address{} + // Get signers from this block. + masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader) + validators := ExtractValidatorsFromBytes(checkpointHeader.Validators) + + if len(validators) < len(masternodes) { + return nil, errors.New("len(m2) is less than len(m1)") + } + if len(masternodes) > 0 { + for i, m1 := range masternodes { + m1m2[m1] = masternodes[validators[i]%int64(len(masternodes))] + } + } + return m1m2, 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) GetRewards(hash common.Hash) map[string]interface{} { + rewards, ok := c.rewards.Get(hash) + if !ok { + return nil + } + return rewards.(map[string]interface{}) +} + +func (c *XDPoS) InsertRewards(hash common.Hash, rewards map[string]interface{}) { + c.rewards.Add(hash, rewards) +} diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go new file mode 100644 index 0000000000..612632a182 --- /dev/null +++ b/consensus/XDPoS/api.go @@ -0,0 +1,100 @@ +// Copyright (c) 2018 XDCchain +// +// 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 + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +// API is a user facing RPC API to allow controlling the signer and voting +// mechanisms of the proof-of-authority scheme. +type API struct { + chain consensus.ChainReader + XDPoS *XDPoS +} + +// GetSnapshot retrieves the state snapshot at a given block. +func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + // Ensure we have an actually valid block and return its snapshot + if header == nil { + return nil, errUnknownBlock + } + return api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) +} + +// GetSnapshotAtHash retrieves the state snapshot at a given block. +func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) { + header := api.chain.GetHeaderByHash(hash) + if header == nil { + return nil, errUnknownBlock + } + return api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) +} + +// GetSigners retrieves the list of authorized signers at the specified block. +func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + // Ensure we have an actually valid block and return the signers from its snapshot + if header == nil { + return nil, errUnknownBlock + } + snap, err := api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.GetSigners(), nil +} + +// 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 + } + snap, err := api.XDPoS.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.GetSigners(), nil +} + +// Proposals returns the current proposals the node tries to uphold and vote on. +func (api *API) Proposals() map[common.Address]bool { + api.XDPoS.lock.RLock() + defer api.XDPoS.lock.RUnlock() + + proposals := make(map[common.Address]bool) + for address, auth := range api.XDPoS.proposals { + proposals[address] = auth + } + return proposals +} diff --git a/consensus/XDPoS/snapshot.go b/consensus/XDPoS/snapshot.go new file mode 100644 index 0000000000..80a4f4e722 --- /dev/null +++ b/consensus/XDPoS/snapshot.go @@ -0,0 +1,311 @@ +// Copyright (c) 2018 XDCchain +// +// 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 + +import ( + "bytes" + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" + lru "github.com/hashicorp/golang-lru" +) + +// Vote represents a single vote that an authorized signer made to modify the +// list of authorizations. +//type Vote struct { +// Signer common.Address `json:"signer"` // Authorized signer that cast this vote +// Block uint64 `json:"block"` // Block number the vote was cast in (expire old votes) +// Address common.Address `json:"address"` // Account being voted on to change its authorization +// Authorize bool `json:"authorize"` // Whether to authorize or deauthorize the voted account +//} + +// Tally is a simple vote tally to keep the current score of votes. Votes that +// go against the proposal aren't counted since it's equivalent to not voting. +//type Tally struct { +// Authorize bool `json:"authorize"` // Whether the vote is about authorizing or kicking someone +// Votes int `json:"votes"` // Number of votes until now wanting to pass the proposal +//} + +// Snapshot is the state of the authorization voting at a given point in time. +type Snapshot struct { + config *params.XDPoSConfig // Consensus engine parameters to fine tune behavior + sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover + + 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 +} + +// 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{ + config: config, + sigcache: sigcache, + Number: number, + Hash: hash, + Signers: make(map[common.Address]struct{}), + Recents: make(map[uint64]common.Address), + Tally: make(map[common.Address]clique.Tally), + } + for _, signer := range signers { + snap.Signers[signer] = struct{}{} + } + return snap +} + +// loadSnapshot loads an existing snapshot from the database. +func loadSnapshot(config *params.XDPoSConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) { + blob, err := db.Get(append([]byte("XDPoS-"), hash[:]...)) + if err != nil { + return nil, err + } + snap := new(Snapshot) + if err := json.Unmarshal(blob, snap); err != nil { + return nil, err + } + snap.config = config + snap.sigcache = sigcache + + return snap, nil +} + +// store inserts the snapshot into the database. +func (s *Snapshot) store(db ethdb.Database) error { + blob, err := json.Marshal(s) + if err != nil { + return err + } + 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{ + config: s.config, + sigcache: s.sigcache, + Number: s.Number, + Hash: s.Hash, + Signers: make(map[common.Address]struct{}), + Recents: make(map[uint64]common.Address), + Votes: make([]*clique.Vote, len(s.Votes)), + Tally: make(map[common.Address]clique.Tally), + } + for signer := range s.Signers { + cpy.Signers[signer] = struct{}{} + } + for block, signer := range s.Recents { + cpy.Recents[block] = signer + } + for address, tally := range s.Tally { + cpy.Tally[address] = tally + } + copy(cpy.Votes, s.Votes) + + return cpy +} + +// 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 { + _, 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 { + // Ensure the vote is meaningful + if !s.validVote(address, authorize) { + return false + } + // Cast the vote into an existing or new tally + if old, ok := s.Tally[address]; ok { + old.Votes++ + s.Tally[address] = old + } else { + s.Tally[address] = clique.Tally{Authorize: authorize, Votes: 1} + } + return true +} + +// uncast removes a previously cast vote from the tally. +func (s *Snapshot) 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 { + return false + } + // Ensure we only revert counted votes + if tally.Authorize != authorize { + return false + } + // Otherwise revert the vote + if tally.Votes > 1 { + tally.Votes-- + s.Tally[address] = tally + } else { + delete(s.Tally, address) + } + return true +} + +// apply creates a new authorization snapshot by applying the given headers to +// the original one. +func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { + // Allow passing in no headers for cleaner code + if len(headers) == 0 { + return s, nil + } + // 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 + } + } + if headers[0].Number.Uint64() != s.Number+1 { + return nil, errInvalidVotingChain + } + // Iterate through the headers and create a new snapshot + snap := s.copy() + + for _, header := range headers { + // Remove any votes on checkpoint blocks + number := header.Number.Uint64() + if number%s.config.Epoch == 0 { + snap.Votes = nil + snap.Tally = make(map[common.Address]clique.Tally) + } + // Delete the oldest signer from the recent list to allow it signing again + if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { + delete(snap.Recents, number-limit) + } + // Resolve the authorization key and check against signers + signer, err := ecrecover(header, s.sigcache) + if err != nil { + return nil, err + } + //FIXME: skip signer checking at this step until a good solution found + //if _, ok := snap.Signers[signer]; !ok { + // return nil, errUnauthorized + //} + //for _, recent := range snap.Recents { + // if recent == signer { + // return nil, errUnauthorized + // } + //} + snap.Recents[number] = signer + + // Header authorized, discard any previous votes from the signer + for i, vote := range snap.Votes { + if vote.Signer == signer && vote.Address == header.Coinbase { + // Uncast the vote from the cached tally + snap.uncast(vote.Address, vote.Authorize) + + // Uncast the vote from the chronological list + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + break // only one vote allowed + } + } + // Tally up the new vote from the signer + var authorize bool + switch { + case bytes.Equal(header.Nonce[:], nonceAuthVote): + authorize = true + case bytes.Equal(header.Nonce[:], nonceDropVote): + authorize = false + default: + return nil, errInvalidVote + } + if snap.cast(header.Coinbase, authorize) { + snap.Votes = append(snap.Votes, &clique.Vote{ + Signer: signer, + Block: number, + Address: header.Coinbase, + Authorize: authorize, + }) + } + // If the vote passed, update the list of signers + if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 { + if tally.Authorize { + snap.Signers[header.Coinbase] = struct{}{} + } else { + delete(snap.Signers, header.Coinbase) + + // Signer list shrunk, delete any leftover recent caches + if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { + delete(snap.Recents, number-limit) + } + // Discard any previous votes the deauthorized signer cast + for i := 0; i < len(snap.Votes); i++ { + if snap.Votes[i].Signer == header.Coinbase { + // Uncast the vote from the cached tally + snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize) + + // Uncast the vote from the chronological list + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + + i-- + } + } + } + // Discard any previous votes around the just changed account + for i := 0; i < len(snap.Votes); i++ { + if snap.Votes[i].Address == header.Coinbase { + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + i-- + } + } + delete(snap.Tally, header.Coinbase) + } + } + snap.Number += uint64(len(headers)) + snap.Hash = headers[len(headers)-1].Hash() + + return snap, nil +} + +// signers retrieves the list of authorized signers in ascending order. +func (s *Snapshot) GetSigners() []common.Address { + signers := make([]common.Address, 0, len(s.Signers)) + for signer := range s.Signers { + signers = append(signers, signer) + } + for i := 0; i < len(signers); i++ { + for j := i + 1; j < len(signers); j++ { + if bytes.Compare(signers[i][:], signers[j][:]) > 0 { + signers[i], signers[j] = signers[j], signers[i] + } + } + } + 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/snapshot_test.go b/consensus/XDPoS/snapshot_test.go new file mode 100644 index 0000000000..56a2120839 --- /dev/null +++ b/consensus/XDPoS/snapshot_test.go @@ -0,0 +1,404 @@ +// Copyright (c) 2018 Xinfin +// +// 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 + +import ( + "bytes" + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" +) + +type testerVote struct { + signer string + voted string + auth bool +} + +// testerAccountPool is a pool to maintain currently active tester accounts, +// mapped from textual names used in the tests below to actual Ethereum private +// keys capable of signing transactions. +type testerAccountPool struct { + accounts map[string]*ecdsa.PrivateKey +} + +func newTesterAccountPool() *testerAccountPool { + return &testerAccountPool{ + accounts: make(map[string]*ecdsa.PrivateKey), + } +} + +func (ap *testerAccountPool) sign(header *types.Header, signer string) { + // Ensure we have a persistent key for the signer + if ap.accounts[signer] == nil { + ap.accounts[signer], _ = crypto.GenerateKey() + } + // Sign the header and embed the signature in extra data + sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer]) + copy(header.Extra[len(header.Extra)-65:], sig) +} + +func (ap *testerAccountPool) address(account string) common.Address { + // Ensure we have a persistent key for the account + if ap.accounts[account] == nil { + ap.accounts[account], _ = crypto.GenerateKey() + } + // Resolve and return the Ethereum address + return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) +} + +// testerChainReader implements consensus.ChainReader to access the genesis +// block. All other methods and requests will panic. +type testerChainReader struct { + db ethdb.Database +} + +func (r *testerChainReader) Config() *params.ChainConfig { return params.AllXDPoSProtocolChanges } +func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") } +func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") } +func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") } +func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header { panic("not supported") } +func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header { + if number == 0 { + return core.GetHeader(r.db, core.GetCanonicalHash(r.db, 0), 0) + } + panic("not supported") +} + +// Tests that voting is evaluated correctly for various simple and complex scenarios. +func TestVoting(t *testing.T) { + // Define the various voting scenarios to test + tests := []struct { + epoch uint64 + signers []string + votes []testerVote + results []string + }{ + { + // Single signer, no votes cast + signers: []string{"A"}, + votes: []testerVote{{signer: "A"}}, + results: []string{"A"}, + }, { + // Single signer, voting to add two others (only accept first, second needs 2 votes) + signers: []string{"A"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: true}, + {signer: "B"}, + {signer: "A", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Two signers, voting to add three others (only accept first two, third needs 3 votes already) + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B", voted: "C", auth: true}, + {signer: "A", voted: "D", auth: true}, + {signer: "B", voted: "D", auth: true}, + {signer: "C"}, + {signer: "A", voted: "E", auth: true}, + {signer: "B", voted: "E", auth: true}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Single signer, dropping itself (weird, but one less cornercase by explicitly allowing this) + signers: []string{"A"}, + votes: []testerVote{ + {signer: "A", voted: "A", auth: false}, + }, + results: []string{}, + }, { + // Two signers, actually needing mutual consent to drop either of them (not fulfilled) + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Two signers, actually needing mutual consent to drop either of them (fulfilled) + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: false}, + {signer: "B", voted: "B", auth: false}, + }, + results: []string{"A"}, + }, { + // Three signers, two of them deciding to drop the third + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Four signers, consensus of two not being enough to drop anyone + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Four signers, consensus of three already being enough to drop someone + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + }, + results: []string{"A", "B", "C"}, + }, { + // Authorizations are counted once per signer per target + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Authorizing multiple accounts concurrently is permitted + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A", voted: "D", auth: true}, + {signer: "B"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: true}, + {signer: "A"}, + {signer: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Deauthorizations are counted once per signer per target + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: false}, + {signer: "B"}, + {signer: "A", voted: "B", auth: false}, + {signer: "B"}, + {signer: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Deauthorizing multiple accounts concurrently is permitted + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + {signer: "A"}, + {signer: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Votes from deauthorized signers are discarded immediately (deauth votes) + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "C", voted: "B", auth: false}, + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Votes from deauthorized signers are discarded immediately (auth votes) + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "C", voted: "B", auth: false}, + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Cascading changes are not allowed, only the account being voted on may change + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + }, + results: []string{"A", "B", "C"}, + }, { + // Changes reaching consensus out of bounds (via a deauth) execute on touch + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + {signer: "A"}, + {signer: "C", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + {signer: "A"}, + {signer: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B", "C"}, + }, { + // Ensure that pending votes don't survive authorization status changes. This + // corner case can only appear if a signer is quickly added, removed and then + // readded (or the inverse), while one of the original voters dropped. If a + // past vote is left cached in the system somewhere, this will interfere with + // the final signer outcome. + signers: []string{"A", "B", "C", "D", "E"}, + votes: []testerVote{ + {signer: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed + {signer: "B", voted: "F", auth: true}, + {signer: "C", voted: "F", auth: true}, + {signer: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") + {signer: "E", voted: "F", auth: false}, + {signer: "B", voted: "F", auth: false}, + {signer: "C", voted: "F", auth: false}, + {signer: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed + {signer: "E", voted: "F", auth: true}, + {signer: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed + {signer: "C", voted: "A", auth: false}, + {signer: "D", voted: "A", auth: false}, + {signer: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed + }, + results: []string{"B", "C", "D", "E", "F"}, + }, { + // Epoch transitions reset all votes to allow chain checkpointing + epoch: 3, + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots) + {signer: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, + } + // Run through the scenarios and test them + for i, tt := range tests { + // Create the account pool and generate the initial set of signers + accounts := newTesterAccountPool() + + signers := make([]common.Address, len(tt.signers)) + for j, signer := range tt.signers { + signers[j] = accounts.address(signer) + } + for j := 0; j < len(signers); j++ { + for k := j + 1; k < len(signers); k++ { + if bytes.Compare(signers[j][:], signers[k][:]) > 0 { + signers[j], signers[k] = signers[k], signers[j] + } + } + } + // Create the genesis block with the initial set of signers + genesis := &core.Genesis{ + ExtraData: make([]byte, extraVanity+common.AddressLength*len(signers)+extraSeal), + } + for j, signer := range signers { + copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:]) + } + // Create a pristine blockchain with the genesis injected + db, _ := ethdb.NewMemDatabase() + genesis.Commit(db) + + // Assemble a chain of headers from the cast votes + headers := make([]*types.Header, len(tt.votes)) + for j, vote := range tt.votes { + headers[j] = &types.Header{ + Number: big.NewInt(int64(j) + 1), + Time: big.NewInt(int64(j) * int64(blockPeriod)), + Coinbase: accounts.address(vote.voted), + Extra: make([]byte, extraVanity+extraSeal), + } + if j > 0 { + headers[j].ParentHash = headers[j-1].Hash() + } + if vote.auth { + copy(headers[j].Nonce[:], nonceAuthVote) + } + accounts.sign(headers[j], vote.signer) + } + // Pass all the headers through XDPoS and ensure tallying succeeds + head := headers[len(headers)-1] + + snap, err := New(¶ms.XDPoSConfig{Epoch: tt.epoch}, db).snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers) + if err != nil { + t.Errorf("test %d: failed to create voting snapshot: %v", i, err) + continue + } + // Verify the final list of signers against the expected ones + signers = make([]common.Address, len(tt.results)) + for j, signer := range tt.results { + signers[j] = accounts.address(signer) + } + for j := 0; j < len(signers); j++ { + for k := j + 1; k < len(signers); k++ { + if bytes.Compare(signers[j][:], signers[k][:]) > 0 { + signers[j], signers[k] = signers[k], signers[j] + } + } + } + result := snap.signers() + if len(result) != len(signers) { + t.Errorf("test %d: signers mismatch: have %x, want %x", i, result, signers) + continue + } + for j := 0; j < len(result); j++ { + if !bytes.Equal(result[j][:], signers[j][:]) { + t.Errorf("test %d, signer %d: signer mismatch: have %x, want %x", i, j, result[j], signers[j]) + } + } + } +} \ No newline at end of file diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 9b9c8612ff..0a04b630be 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -25,7 +25,6 @@ import ( "sync" "time" - "fmt" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -40,19 +39,16 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru" ) const ( - inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory - inmemorySignatures = 4096 // Number of recent block signatures to keep in memory - wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers -) + checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database + inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + inmemorySignatures = 4096 // Number of recent block signatures to keep in memory -type Masternode struct { - Address common.Address - Stake string -} + wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers +) // Clique proof-of-authority protocol constants. var ( @@ -210,8 +206,6 @@ type Clique struct { signer common.Address // Ethereum address of the signing key signFn SignerFn // Signer function to authorize hashes with lock sync.RWMutex // Protects the signer fields - - HookReward func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) error } // New creates a Clique proof-of-authority consensus engine with the initial @@ -283,6 +277,9 @@ func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, } // 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 @@ -370,71 +367,6 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *type return c.verifySeal(chain, header, parents) } -func (c *Clique) 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 *Clique) 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 *Clique) 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 *Clique) GetPeriod() uint64 { return c.config.Period } - -func YourTurn(masternodes []common.Address, snap *Snapshot, header *types.Header, cur common.Address) (int, int, bool, error) { - if len(masternodes) == 0 { - return -1, -1, true, nil - } - pre := common.Address{} - // masternode[0] has chance to create block 1 - var err error - preIndex := -1 - if header.Number.Uint64() != 0 { - pre, err = ecrecover(header, snap.sigcache) - if err != nil { - return 0, 0, false, err - } - preIndex = position(masternodes, pre) - } - curIndex := position(masternodes, cur) - log.Info("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", cur, "position", curIndex) - for i, s := range masternodes { - fmt.Printf("%d - %s\n", i, s.String()) - } - if (preIndex+1)%len(masternodes) == curIndex { - return preIndex, curIndex, true, nil - } - return preIndex, curIndex, false, nil -} - // snapshot retrieves the authorization snapshot at a given point in time. func (c *Clique) 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 @@ -449,8 +381,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo 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 number%checkpointInterval == 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 @@ -504,7 +435,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo 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 snap.Number%checkpointInterval == 0 && len(headers) > 0 { if err = snap.store(c.db); err != nil { return nil, err } @@ -549,36 +480,25 @@ func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, p if err != nil { return err } - masternodes := c.GetMasternodes(chain, header) - mstring := []string{} - for _, m := range masternodes { - mstring = append(mstring, m.String()) - } - nstring := []string{} - for _, n := range snap.signers() { - nstring = append(nstring, n.String()) - } if _, ok := snap.Signers[signer]; !ok { - valid := false - for _, m := range masternodes { - if m == signer { - valid = true - break - } - } - if !valid { - log.Debug("Unauthorized signer found", "block number", number, "signer", signer.String(), "masternodes", mstring, "snapshot from parent block", nstring) - return errUnauthorized - } + return errUnauthorized } for seen, recent := range snap.Recents { if recent == signer { // Signer is among recents, only fail if the current block doesn't shift it out - if limit := uint64(len(masternodes)/2 + 1); seen > number-limit { + if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit { return errUnauthorized } } } + // Ensure that the difficulty corresponds to the turn-ness of the signer + inturn := snap.inturn(header.Number.Uint64(), signer) + if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { + return errInvalidDifficulty + } + if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { + return errInvalidDifficulty + } return nil } @@ -647,51 +567,9 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro return nil } -func (c *Clique) 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 - } - currentSigners := snap.signers() - proposedSigners := make(map[common.Address]struct{}) - // count all addresses in ms to be masternode - for _, m := range ms { - proposedSigners[m.Address] = struct{}{} - snap.Signers[m.Address] = struct{}{} - } - // deactivate current masternodes which aren't in ms - for _, s := range currentSigners { - if _, ok := proposedSigners[s]; !ok { - delete(snap.Signers, s) - } - } - nm := []string{} - newSigners := snap.signers() - for _, n := range newSigners { - nm = append(nm, n.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 *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - // set block reward - // FIXME: unit Ether could be too plump - number := header.Number.Uint64() - rCheckpoint := chain.Config().Clique.RewardCheckpoint - - if c.HookReward != nil && number%rCheckpoint == 0 { - if err := c.HookReward(chain, state, header); err != nil { - return nil, err - } - } - // No block rewards in PoA, so the state remains as is and uncles are dropped header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.UncleHash = types.CalcUncleHash(nil) @@ -734,25 +612,15 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch if err != nil { return nil, err } - masternodes := c.GetMasternodes(chain, header) + masternodes := []common.Address{} if _, authorized := snap.Signers[signer]; !authorized { - valid := false - for _, m := range masternodes { - if m == signer { - valid = true - break - } - } - if !valid { - return nil, errUnauthorized - } + return nil, errUnauthorized } // If we're amongst the recent signers, wait for the next block for seen, recent := range snap.Recents { if recent == signer { // Signer is among recents, only wait if the current block doesn't shift it out if limit := uint64(len(masternodes)/2 + 1); number < limit || seen > number-limit { - log.Info("Debugging", "len(masternodes)", len(masternodes), "number", number, "limit", limit, "seen", seen, "recent", recent.String(), "snap.Recents", snap.Recents) log.Info("Signed recently, must wait for others") <-stop return nil, nil @@ -763,7 +631,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple if header.Difficulty.Cmp(diffNoTurn) == 0 { // It's not our turn explicitly to sign, delay it a bit - wiggle := time.Duration(len(masternodes)/2+1) * wiggleTime + wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime delay += time.Duration(rand.Int63n(int64(wiggle))) log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) @@ -815,21 +683,4 @@ func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API { Service: &API{chain: chain, clique: c}, Public: false, }} -} - -func (c *Clique) RecoverSigner(header *types.Header) (common.Address, error) { - return ecrecover(header, c.signatures) -} - -// Get master nodes over extra data of previous checkpoint block. -func (c *Clique) 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)-extraVanity-extraSeal)/common.AddressLength) - for i := 0; i < len(masternodes); i++ { - copy(masternodes[i][:], preCheckpointHeader.Extra[extraVanity+i*common.AddressLength:]) - } - return masternodes } \ No newline at end of file diff --git a/consensus/clique/snapshot.go b/consensus/clique/snapshot.go index 1121f2443a..d1d9bfb00c 100644 --- a/consensus/clique/snapshot.go +++ b/consensus/clique/snapshot.go @@ -45,7 +45,7 @@ type Tally struct { // Snapshot is the state of the authorization voting at a given point in time. type Snapshot struct { - config *params.CliqueConfig // Consensus engine parameters to fine tune behavior + config *params.XDPoSConfig // Consensus engine parameters to fine tune behavior sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover Number uint64 `json:"number"` // Block number where the snapshot was created @@ -59,7 +59,7 @@ 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.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot { +func newSnapshot(config *params.XDPoSConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot { snap := &Snapshot{ config: config, sigcache: sigcache, @@ -76,7 +76,7 @@ func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uin } // loadSnapshot loads an existing snapshot from the database. -func loadSnapshot(config *params.CliqueConfig, 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) (*Snapshot, error) { blob, err := db.Get(append([]byte("clique-"), hash[:]...)) if err != nil { return nil, err @@ -205,15 +205,15 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { if err != nil { return nil, err } - //FIXME: skip signer checking at this step until a good solution found - //if _, ok := snap.Signers[signer]; !ok { - // return nil, errUnauthorized - //} - //for _, recent := range snap.Recents { - // if recent == signer { - // return nil, errUnauthorized - // } - //} + if _, ok := snap.Signers[signer]; !ok { + return nil, errUnauthorized + } + for _, recent := range snap.Recents { + if recent == signer { + return nil, errUnauthorized + } + } + snap.Recents[number] = signer // Header authorized, discard any previous votes from the signer diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index 8b51e6e094..826e0ed57c 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -74,7 +74,7 @@ type testerChainReader struct { db ethdb.Database } -func (r *testerChainReader) Config() *params.ChainConfig { return params.AllCliqueProtocolChanges } +func (r *testerChainReader) Config() *params.ChainConfig { return params.AllXDPoSProtocolChanges } func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") } func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") } func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") } @@ -374,7 +374,7 @@ func TestVoting(t *testing.T) { // Pass all the headers through clique and ensure tallying succeeds head := headers[len(headers)-1] - snap, err := New(¶ms.CliqueConfig{Epoch: tt.epoch}, db).snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers) + snap, err := New(¶ms.XDPoSConfig{Epoch: tt.epoch}, db).snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers) if err != nil { t.Errorf("test %d: failed to create voting snapshot: %v", i, err) continue diff --git a/contracts/utils.go b/contracts/utils.go index 27473c01f6..a3057efeb3 100644 --- a/contracts/utils.go +++ b/contracts/utils.go @@ -31,7 +31,7 @@ type rewardLog struct { // Send tx sign for block number to smart contract blockSigner. func CreateTransactionSign(chainConfig *params.ChainConfig, pool *core.TxPool, manager *accounts.Manager, block *types.Block) error { - if chainConfig.Clique != nil { + if chainConfig.XDPoS != nil { // Find active account. account := accounts.Account{} var wallet accounts.Wallet diff --git a/core/blockchain.go b/core/blockchain.go index 6dfdd44093..4851959684 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1189,13 +1189,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty stats.processed++ stats.usedGas += usedGas stats.report(chain, i, bc.stateCache.TrieDB().Size()) - if i == len(chain)-1 && bc.chainConfig.Clique != nil { + if i == len(chain)-1 && bc.chainConfig.XDPoS != nil { // epoch block - if (chain[i].NumberU64() % bc.chainConfig.Clique.Epoch) == 0 { + if (chain[i].NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 { CheckpointCh <- 1 } // prepare set of masternodes for the next epoch - if (chain[i].NumberU64() % bc.chainConfig.Clique.Epoch) == (bc.chainConfig.Clique.Epoch - bc.chainConfig.Clique.Gap) { + if (chain[i].NumberU64() % bc.chainConfig.XDPoS.Epoch) == (bc.chainConfig.XDPoS.Epoch - bc.chainConfig.Clique.Gap) { M1Ch <- 1 } } diff --git a/core/chain_makers.go b/core/chain_makers.go index 31c9e3fb77..369e6b01ec 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -176,7 +176,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) { - // TODO(karalabe): This is needed for clique, which depends on multiple blocks. + // TODO(karalabe): This is needed for XDPoS, which depends on multiple blocks. // It's nonetheless ugly to spin up a blockchain here. Get rid of this somehow. blockchain, _ := NewBlockChain(db, nil, config, engine, vm.Config{}) defer blockchain.Stop() diff --git a/core/genesis.go b/core/genesis.go index b6ead2250a..c5445151d3 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -348,8 +348,8 @@ func DefaultRinkebyGenesisBlock() *Genesis { // be seeded with the func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis { // Override the default period to the user requested one - config := *params.AllCliqueProtocolChanges - config.Clique.Period = period + config := *params.AllXDPoSProtocolChanges + config.XDPoS.Period = period // Assemble and return the genesis with the precompiles and faucet pre-funded return &Genesis{ diff --git a/eth/backend.go b/eth/backend.go index ef0973c8df..834268387a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -29,7 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/XDPoS" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/contracts" "github.com/ethereum/go-ethereum/contracts/validator/contract" @@ -186,8 +186,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { // Set global ipc endpoint. eth.blockchain.IPCEndpoint = ctx.GetConfig().IPCEndpoint() - if eth.chainConfig.Clique != nil { - c := eth.engine.(*clique.Clique) + if eth.chainConfig.XDPoS != nil { + c := eth.engine.(*XDPoS.XDPoS) // Inject hook for send tx sign to smartcontract after insert block into chain. importedHook := func(block *types.Block) { @@ -209,18 +209,18 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } eth.protocolManager.fetcher.SetImportedHook(importedHook) - // Hook reward for clique validator. + // Hook reward for XDPoS validator. c.HookReward = func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) error { client, err := eth.blockchain.GetClient() if err != nil { log.Error("Fail to connect IPC client for blockSigner", "error", err) } number := header.Number.Uint64() - rCheckpoint := chain.Config().Clique.RewardCheckpoint + rCheckpoint := chain.Config().XDPoS.RewardCheckpoint if number > 0 && number-rCheckpoint > 0 { // Get signers in blockSigner smartcontract. addr := common.HexToAddress(common.BlockSigners) - chainReward := new(big.Int).Mul(new(big.Int).SetUint64(chain.Config().Clique.Reward), new(big.Int).SetUint64(params.Ether)) + chainReward := new(big.Int).Mul(new(big.Int).SetUint64(chain.Config().XDPoS.Reward), new(big.Int).SetUint64(params.Ether)) totalSigner := new(uint64) signers, err := contracts.GetRewardForCheckpoint(chain, addr, number, rCheckpoint, client, totalSigner) if err != nil { @@ -285,9 +285,9 @@ func CreateDB(ctx *node.ServiceContext, config *Config, name string) (ethdb.Data // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chainConfig *params.ChainConfig, db ethdb.Database) consensus.Engine { - // If proof-of-authority is requested, set it up - if chainConfig.Clique != nil { - return clique.New(chainConfig.Clique, db) + // If proof-of-stake-voting is requested, set it up + if chainConfig.XDPoS != nil { + return XDPoS.New(chainConfig.XDPoS, db) } // Otherwise assume proof-of-work switch { @@ -413,9 +413,9 @@ func (s *Ethereum) ValidateStaker() (bool, error) { if err != nil { return false, err } - if s.chainConfig.Clique != nil { + if s.chainConfig.XDPoS != nil { //check if miner's wallet is in set of validators - c := s.engine.(*clique.Clique) + c := s.engine.(*XDPoS.XDPoS) snap, err := c.GetSnapshot(s.blockchain, s.blockchain.CurrentHeader()) if err != nil { return false, fmt.Errorf("Can't verify miner: %v", err) @@ -425,18 +425,18 @@ func (s *Ethereum) ValidateStaker() (bool, error) { return false, nil } } else { - return false, fmt.Errorf("Only verify miners in Clique protocol") + return false, fmt.Errorf("Only verify miners in XDPoS protocol") } return true, nil } // Store new set of masternodes into local db -func (s *Ethereum) UpdateMasternodes(ms []clique.Masternode) error { +func (s *Ethereum) UpdateMasternodes(ms []XDPoS.Masternode) error { // get snapshot from local db - if s.chainConfig.Clique == nil { - return errors.New("not clique") + if s.chainConfig.XDPoS == nil { + return errors.New("not XDPoS") } - c := s.engine.(*clique.Clique) + c := s.engine.(*XDPoS.XDPoS) return c.UpdateMasternodes(s.blockchain, s.blockchain.CurrentHeader(), ms) } @@ -446,13 +446,13 @@ func (s *Ethereum) StartStaking(local bool) error { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } - if clique, ok := s.engine.(*clique.Clique); ok { + if XDPoS, ok := s.engine.(*XDPoS.XDPoS); ok { wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) return fmt.Errorf("signer missing: %v", err) } - clique.Authorize(eb, wallet.SignHash) + XDPoS.Authorize(eb, wallet.SignHash) } if local { // If local (CPU) mining is started, we can disable the transaction rejection diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4105453d52..510c3ca4db 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/XDPos" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/contracts" "github.com/ethereum/go-ethereum/core" @@ -862,12 +862,12 @@ func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx log.Error("Fail to get signers from block signer SC.", "error", err) } // Get block epoc latest. - if s.b.ChainConfig().Clique != nil { + if s.b.ChainConfig().XDPos != nil { engine := s.b.GetEngine() - lastCheckpointNumber := rpc.BlockNumber(b.Number().Uint64() - (b.Number().Uint64() % s.b.ChainConfig().Clique.Epoch)) + lastCheckpointNumber := rpc.BlockNumber(b.Number().Uint64() - (b.Number().Uint64() % s.b.ChainConfig().XDPoS.Epoch)) prevCheckpointBlock, _ := s.b.BlockByNumber(ctx, lastCheckpointNumber) if prevCheckpointBlock != nil { - masternodes := engine.(*clique.Clique).GetMasternodesFromCheckpointHeader(prevCheckpointBlock.Header(), b.Number().Uint64(), s.b.ChainConfig().Clique.Epoch) + masternodes := engine.(*XDPoS.XDPoS).GetMasternodesFromCheckpointHeader(prevCheckpointBlock.Header(), b.Number().Uint64(), s.b.ChainConfig().Clique.Epoch) countFinality := 0 for _, masternode := range masternodes { for _, signer := range signers { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 9d6ce8c6c8..dc582baab4 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -20,7 +20,7 @@ package web3ext var Modules = map[string]string{ "admin": Admin_JS, "chequebook": Chequebook_JS, - "clique": Clique_JS, + "XDPoS": XDPoS_JS, "debug": Debug_JS, "eth": Eth_JS, "miner": Miner_JS, @@ -63,13 +63,13 @@ web3._extend({ }); ` -const Clique_JS = ` +const XDPoS_JS = ` web3._extend({ - property: 'clique', + property: 'XDPoS', methods: [ new web3._extend.Method({ name: 'getSnapshot', - call: 'clique_getSnapshot', + call: 'XDPoS_getSnapshot', params: 1, inputFormatter: [null] }), @@ -80,30 +80,30 @@ web3._extend({ }), new web3._extend.Method({ name: 'getSigners', - call: 'clique_getSigners', + call: 'XDPoS_getSigners', params: 1, inputFormatter: [null] }), new web3._extend.Method({ name: 'getSignersAtHash', - call: 'clique_getSignersAtHash', + call: 'XDPoS_getSignersAtHash', params: 1 }), new web3._extend.Method({ name: 'propose', - call: 'clique_propose', + call: 'XDPoS_propose', params: 2 }), new web3._extend.Method({ name: 'discard', - call: 'clique_discard', + call: 'XDPoS_discard', params: 1 }), ], properties: [ new web3._extend.Property({ name: 'proposals', - getter: 'clique_proposals' + getter: 'XDPoS_proposals' }), ] }); diff --git a/miner/worker.go b/miner/worker.go index 652b1f6f31..7a1f710942 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/XDPoS" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/contracts" "github.com/ethereum/go-ethereum/core" @@ -275,7 +275,7 @@ func (self *worker) update() { self.currentMu.Unlock() } else { // If we're mining, but nothing is being processed, wake on new transactions - if self.config.Clique != nil && self.config.Clique.Period == 0 { + if self.config.XDPoS != nil && self.config.XDPoS.Period == 0 { self.commitNewWork() } } @@ -342,8 +342,8 @@ func (self *worker) wait() { self.commitNewWork() } - if self.config.Clique != nil { - c := self.engine.(*clique.Clique) + 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.") @@ -449,17 +449,17 @@ func (self *worker) commitNewWork() { // Only try to commit new work if we are mining if atomic.LoadInt32(&self.mining) == 1 { // check if we are right after parent's coinbase in the list - // only go with Clique - if self.config.Clique != nil { + // only go with XDPoS + if self.config.XDPoS != nil { // get masternodes set from latest checkpoint - c := self.engine.(*clique.Clique) + c := self.engine.(*XDPoS.XDPoS) masternodes := c.GetMasternodes(self.chain, parent.Header()) snap, err := c.GetSnapshot(self.chain, parent.Header()) if err != nil { log.Error("Failed when trying to commit new work", "err", err) return } - preIndex, curIndex, ok, err := clique.YourTurn(masternodes, snap, parent.Header(), self.coinbase) + preIndex, curIndex, ok, err := XDPoS.YourTurn(masternodes, snap, parent.Header(), self.coinbase) if err != nil { log.Error("Failed when trying to commit new work", "err", err) return @@ -586,13 +586,13 @@ func (self *worker) commitNewWork() { log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart))) self.unconfirmed.Shift(work.Block.NumberU64() - 1) } - if work.config.Clique != nil { + if work.config.XDPoS != nil { // epoch block - if (work.Block.NumberU64() % work.config.Clique.Epoch) == 0 { + if (work.Block.NumberU64() % work.config.XDPoS.Epoch) == 0 { core.CheckpointCh <- 1 } // prepare set of masternodes for the next epoch - if (work.Block.NumberU64() % work.config.Clique.Epoch) == (work.config.Clique.Epoch - work.config.Clique.Gap) { + if (work.Block.NumberU64() % work.config.XDPoS.Epoch) == (work.config.XDPoS.Epoch - work.config.XDPoS.Gap) { core.M1Ch <- 1 } } diff --git a/params/config.go b/params/config.go index 998282b899..226b6ed473 100644 --- a/params/config.go +++ b/params/config.go @@ -71,7 +71,7 @@ var ( EIP158Block: big.NewInt(3), ByzantiumBlock: big.NewInt(1035301), ConstantinopleBlock: nil, - Clique: &CliqueConfig{ + XDPoS: &XDPoSConfig{ Period: 15, Epoch: 30000, }, @@ -82,16 +82,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil,nil} - // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced - // and accepted by the Ethereum core developers into the Clique consensus. + // AllXDPoSProtocolChanges contains every protocol change (EIPs) introduced + // and accepted by the Ethereum core developers into the XDPoS consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllXDPoSProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil,nil, &XDPoSConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil,nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -121,6 +121,7 @@ type ChainConfig struct { // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` + XDPoS *XDPoSConfig `json:"XDPoS,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -133,10 +134,8 @@ func (c *EthashConfig) String() string { // CliqueConfig is the consensus engine configs for proof-of-authority based sealing. type CliqueConfig struct { - Period uint64 `json:"period"` // Number of seconds between blocks to enforce - Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint - Reward uint64 `json:"reward"` // Block reward - unit Ether - RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards. + Period uint64 `json:"period"` // Number of seconds between blocks to enforce + Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint } // String implements the stringer interface, returning the consensus engine details. @@ -144,14 +143,29 @@ func (c *CliqueConfig) String() string { return "clique" } + +// XDPoSConfig is the consensus engine configs for proof-of-stake-voting based sealing. +type XDPoSConfig struct { + Period uint64 `json:"period"` // Number of seconds between blocks to enforce + Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint + Reward uint64 `json:"reward"` // Block reward - unit Ether + RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards. + Gap uint64 `json:"gap"` // Gap time preparing for the next epoch +} + +// String implements the stringer interface, returning the consensus engine details. +func (c *XDPoSConfig) String() string { + return "XDPoS" +} + // String implements the fmt.Stringer interface. func (c *ChainConfig) String() string { var engine interface{} switch { case c.Ethash != nil: engine = c.Ethash - case c.Clique != nil: - engine = c.Clique + case c.XDPoS != nil: + engine = c.XDPoS default: engine = "unknown" }