diff --git a/consensus/dpos/api.go b/consensus/dpos/api.go deleted file mode 100644 index 2748b0efd1..0000000000 --- a/consensus/dpos/api.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package dpos - -import ( - "github.com/meitu/go-ethereum/common" - "github.com/meitu/go-ethereum/consensus" - "github.com/meitu/go-ethereum/core/types" - "github.com/meitu/go-ethereum/rpc" - - "math/big" -) - -// API is a user facing RPC API to allow controlling the delegate and voting -// mechanisms of the delegated-proof-of-stake -type API struct { - chain consensus.ChainReader - dpos *Dpos -} - -// GetValidators retrieves the list of the validators at specified block -func (api *API) GetValidators(number *rpc.BlockNumber) ([]common.Address, error) { - var header *types.Header - if number == nil || *number == rpc.LatestBlockNumber { - header = api.chain.CurrentHeader() - } else { - header = api.chain.GetHeaderByNumber(uint64(number.Int64())) - } - if header == nil { - return nil, errUnknownBlock - } - - epochTrie, err := types.NewEpochTrie(header.DposContext.EpochHash, api.dpos.db) - if err != nil { - return nil, err - } - dposContext := types.DposContext{} - dposContext.SetEpoch(epochTrie) - validators, err := dposContext.GetValidators() - if err != nil { - return nil, err - } - return validators, nil -} - -// GetConfirmedBlockNumber retrieves the latest irreversible block -func (api *API) GetConfirmedBlockNumber() (*big.Int, error) { - var err error - header := api.dpos.confirmedBlockHeader - if header == nil { - header, err = api.dpos.loadConfirmedBlockHeader(api.chain) - if err != nil { - return nil, err - } - } - return header.Number, nil -} diff --git a/consensus/dpos/dpos.go b/consensus/dpos/dpos.go deleted file mode 100644 index d53d0f8cba..0000000000 --- a/consensus/dpos/dpos.go +++ /dev/null @@ -1,532 +0,0 @@ -package dpos - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "math/big" - "sync" - "time" - - "github.com/pavelkrolevets/go-ethereum/accounts" - "github.com/pavelkrolevets/go-ethereum/common" - "github.com/pavelkrolevets/go-ethereum/consensus" - "github.com/pavelkrolevets/go-ethereum/consensus/misc" - "github.com/pavelkrolevets/go-ethereum/core/state" - "github.com/pavelkrolevets/go-ethereum/core/types" - "github.com/pavelkrolevets/go-ethereum/crypto" - "github.com/pavelkrolevets/go-ethereum/crypto/sha3" - "github.com/pavelkrolevets/go-ethereum/ethdb" - "github.com/pavelkrolevets/go-ethereum/log" - "github.com/pavelkrolevets/go-ethereum/params" - "github.com/pavelkrolevets/go-ethereum/rlp" - "github.com/pavelkrolevets/go-ethereum/rpc" - "github.com/pavelkrolevets/go-ethereum/trie" - lru "github.com/hashicorp/golang-lru" -) - -const ( - 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 - inmemorySignatures = 4096 // Number of recent block signatures to keep in memory - - blockInterval = int64(1) - epochInterval = int64(86400) - maxValidatorSize = 1 - safeSize = 1 //maxValidatorSize*2/3 + 1 - consensusSize = 1 //maxValidatorSize*2/3 + 1 -) - -var ( - big0 = big.NewInt(0) - big8 = big.NewInt(8) - big32 = big.NewInt(32) - - frontierBlockReward *big.Int = big.NewInt(5e+18) // Block reward in wei for successfully mining a block - byzantiumBlockReward *big.Int = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium - - timeOfFirstBlock = int64(0) - - confirmedBlockHead = []byte("confirmed-block-head") -) - -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") - // 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") - // 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 = 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") - ErrWaitForPrevBlock = errors.New("wait for last block arrived") - ErrMintFutureBlock = errors.New("mint the future block") - ErrMismatchSignerAndValidator = errors.New("mismatch block signer and validator") - ErrInvalidBlockValidator = errors.New("invalid block validator") - ErrInvalidMintBlockTime = errors.New("invalid time to mint the block") - ErrNilBlockHeader = errors.New("nil block header returned") -) -var ( - uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. -) - -type Dpos struct { - config *params.DposConfig // Consensus engine configuration parameters - db ethdb.Database // Database to store and retrieve snapshot checkpoints - - signer common.Address - signFn SignerFn - signatures *lru.ARCCache // Signatures of recent blocks to speed up mining - confirmedBlockHeader *types.Header - - mu sync.RWMutex - stop chan bool -} - -type SignerFn func(accounts.Account, []byte) ([]byte, error) - -// NOTE: sigHash was copy from clique -// sigHash returns the hash which is used as input for the proof-of-authority -// 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.Validator, - 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, - header.DposContext.Root(), - }) - hasher.Sum(hash[:0]) - return hash -} - -func New(config *params.DposConfig, db ethdb.Database) *Dpos { - signatures, _ := lru.NewARC(inmemorySignatures) - return &Dpos{ - config: config, - db: db, - signatures: signatures, - } -} - -func (d *Dpos) Author(header *types.Header) (common.Address, error) { - return header.Validator, nil -} - -func (d *Dpos) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { - return d.verifyHeader(chain, header, nil) -} - -func (d *Dpos) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { - if header.Number == nil { - return errUnknownBlock - } - number := header.Number.Uint64() - // Unnecssary to verify the block from feature - if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 { - return consensus.ErrFutureBlock - } - // 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 mix digest is zero as we don't have fork protection currently - if header.MixDigest != (common.Hash{}) { - return errInvalidMixDigest - } - // Difficulty always 1 - if header.Difficulty.Uint64() != 1 { - return errInvalidDifficulty - } - // Ensure that the block doesn't contain any uncles which are meaningless in DPoS - 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 - } - - 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()+uint64(blockInterval) > header.Time.Uint64() { - return ErrInvalidTimestamp - } - return nil -} - -func (d *Dpos) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { - abort := make(chan struct{}) - results := make(chan error, len(headers)) - - go func() { - for i, header := range headers { - err := d.verifyHeader(chain, header, headers[:i]) - select { - case <-abort: - return - case results <- err: - } - } - }() - return abort, results -} - -// VerifyUncles implements consensus.Engine, always returning an error for any -// uncles as this consensus mechanism doesn't permit uncles. -func (d *Dpos) 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 (d *Dpos) VerifySeal(chain consensus.ChainReader, header *types.Header) error { - return d.verifySeal(chain, header, nil) -} - -func (d *Dpos) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { - // Verifying the genesis block is not supported - number := header.Number.Uint64() - if number == 0 { - return errUnknownBlock - } - var parent *types.Header - if len(parents) > 0 { - parent = parents[len(parents)-1] - } else { - parent = chain.GetHeader(header.ParentHash, number-1) - } - dposContext, err := types.NewDposContextFromProto(d.db, parent.DposContext) - if err != nil { - return err - } - epochContext := &EpochContext{DposContext: dposContext} - validator, err := epochContext.lookupValidator(header.Time.Int64()) - if err != nil { - return err - } - if err := d.verifyBlockSigner(validator, header); err != nil { - return err - } - return d.updateConfirmedBlockHeader(chain) -} - -func (d *Dpos) verifyBlockSigner(validator common.Address, header *types.Header) error { - signer, err := ecrecover(header, d.signatures) - if err != nil { - return err - } - if bytes.Compare(signer.Bytes(), validator.Bytes()) != 0 { - return ErrInvalidBlockValidator - } - if bytes.Compare(signer.Bytes(), header.Validator.Bytes()) != 0 { - return ErrMismatchSignerAndValidator - } - return nil -} - -func (d *Dpos) updateConfirmedBlockHeader(chain consensus.ChainReader) error { - if d.confirmedBlockHeader == nil { - header, err := d.loadConfirmedBlockHeader(chain) - if err != nil { - header = chain.GetHeaderByNumber(0) - if header == nil { - return err - } - } - d.confirmedBlockHeader = header - } - - curHeader := chain.CurrentHeader() - epoch := int64(-1) - validatorMap := make(map[common.Address]bool) - for d.confirmedBlockHeader.Hash() != curHeader.Hash() && - d.confirmedBlockHeader.Number.Uint64() < curHeader.Number.Uint64() { - curEpoch := curHeader.Time.Int64() / epochInterval - if curEpoch != epoch { - epoch = curEpoch - validatorMap = make(map[common.Address]bool) - } - // fast return - // if block number difference less consensusSize-witnessNum - // there is no need to check block is confirmed - if curHeader.Number.Int64()-d.confirmedBlockHeader.Number.Int64() < int64(consensusSize-len(validatorMap)) { - log.Debug("Dpos fast return", "current", curHeader.Number.String(), "confirmed", d.confirmedBlockHeader.Number.String(), "witnessCount", len(validatorMap)) - return nil - } - validatorMap[curHeader.Validator] = true - if len(validatorMap) >= consensusSize { - d.confirmedBlockHeader = curHeader - if err := d.storeConfirmedBlockHeader(d.db); err != nil { - return err - } - log.Debug("dpos set confirmed block header success", "currentHeader", curHeader.Number.String()) - return nil - } - curHeader = chain.GetHeaderByHash(curHeader.ParentHash) - if curHeader == nil { - return ErrNilBlockHeader - } - } - return nil -} - -func (s *Dpos) loadConfirmedBlockHeader(chain consensus.ChainReader) (*types.Header, error) { - key, err := s.db.Get(confirmedBlockHead) - if err != nil { - return nil, err - } - header := chain.GetHeaderByHash(common.BytesToHash(key)) - if header == nil { - return nil, ErrNilBlockHeader - } - return header, nil -} - -// store inserts the snapshot into the database. -func (s *Dpos) storeConfirmedBlockHeader(db ethdb.Database) error { - return db.Put(confirmedBlockHead, s.confirmedBlockHeader.Hash().Bytes()) -} - -func (d *Dpos) Prepare(chain consensus.ChainReader, header *types.Header) error { - header.Nonce = types.BlockNonce{} - number := header.Number.Uint64() - if len(header.Extra) < extraVanity { - header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) - } - header.Extra = header.Extra[:extraVanity] - header.Extra = append(header.Extra, make([]byte, extraSeal)...) - parent := chain.GetHeader(header.ParentHash, number-1) - if parent == nil { - return consensus.ErrUnknownAncestor - } - header.Difficulty = d.CalcDifficulty(chain, header.Time.Uint64(), parent) - header.Validator = d.signer - return nil -} - -func AccumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { - // Select the correct block reward based on chain progression - blockReward := frontierBlockReward - if config.IsByzantium(header.Number) { - blockReward = byzantiumBlockReward - } - // Accumulate the rewards for the miner and any included uncles - reward := new(big.Int).Set(blockReward) - state.AddBalance(header.Coinbase, reward) -} - -func (d *Dpos) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, - uncles []*types.Header, receipts []*types.Receipt, dposContext *types.DposContext) (*types.Block, error) { - // Accumulate block rewards and commit the final state root - AccumulateRewards(chain.Config(), state, header, uncles) - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - - parent := chain.GetHeaderByHash(header.ParentHash) - epochContext := &EpochContext{ - statedb: state, - DposContext: dposContext, - TimeStamp: header.Time.Int64(), - } - if timeOfFirstBlock == 0 { - if firstBlockHeader := chain.GetHeaderByNumber(1); firstBlockHeader != nil { - timeOfFirstBlock = firstBlockHeader.Time.Int64() - } - } - genesis := chain.GetHeaderByNumber(0) - err := epochContext.tryElect(genesis, parent) - if err != nil { - return nil, fmt.Errorf("got error when elect next epoch, err: %s", err) - } - - //update mint count trie - updateMintCnt(parent.Time.Int64(), header.Time.Int64(), header.Validator, dposContext) - header.DposContext = dposContext.ToProto() - return types.NewBlock(header, txs, uncles, receipts), nil -} - -func (d *Dpos) checkDeadline(lastBlock *types.Block, now int64) error { - prevSlot := PrevSlot(now) - nextSlot := NextSlot(now) - if lastBlock.Time().Int64() >= nextSlot { - return ErrMintFutureBlock - } - // last block was arrived, or time's up - if lastBlock.Time().Int64() == prevSlot || nextSlot-now <= 1 { - return nil - } - return ErrWaitForPrevBlock -} - -func (d *Dpos) CheckValidator(lastBlock *types.Block, now int64) error { - if err := d.checkDeadline(lastBlock, now); err != nil { - return err - } - dposContext, err := types.NewDposContextFromProto(d.db, lastBlock.Header().DposContext) - if err != nil { - return err - } - epochContext := &EpochContext{DposContext: dposContext} - validator, err := epochContext.lookupValidator(now) - if err != nil { - return err - } - if (validator == common.Address{}) || bytes.Compare(validator.Bytes(), d.signer.Bytes()) != 0 { - return ErrInvalidBlockValidator - } - return nil -} - -// Seal generates a new block for the given input block with the local miner's -// seal place on top. -func (d *Dpos) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { - header := block.Header() - number := header.Number.Uint64() - // Sealing the genesis block is not supported - if number == 0 { - return nil, errUnknownBlock - } - now := time.Now().Unix() - delay := NextSlot(now) - now - if delay > 0 { - select { - case <-stop: - return nil, nil - case <-time.After(time.Duration(delay) * time.Second): - } - } - block.Header().Time.SetInt64(time.Now().Unix()) - - // time's up, sign the block - sighash, err := d.signFn(accounts.Account{Address: d.signer}, sigHash(header).Bytes()) - if err != nil { - return nil, err - } - copy(header.Extra[len(header.Extra)-extraSeal:], sighash) - return block.WithSeal(header), nil -} - -func (d *Dpos) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { - return big.NewInt(1) -} - -func (d *Dpos) APIs(chain consensus.ChainReader) []rpc.API { - return []rpc.API{{ - Namespace: "dpos", - Version: "1.0", - Service: &API{chain: chain, dpos: d}, - Public: true, - }} -} - -func (d *Dpos) Authorize(signer common.Address, signFn SignerFn) { - d.mu.Lock() - d.signer = signer - d.signFn = signFn - d.mu.Unlock() -} - -// 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 -} - -func PrevSlot(now int64) int64 { - return int64((now-1)/blockInterval) * blockInterval -} - -func NextSlot(now int64) int64 { - return int64((now+blockInterval-1)/blockInterval) * blockInterval -} - -// update counts in MintCntTrie for the miner of newBlock -func updateMintCnt(parentBlockTime, currentBlockTime int64, validator common.Address, dposContext *types.DposContext) { - currentMintCntTrie := dposContext.MintCntTrie() - currentEpoch := parentBlockTime / epochInterval - currentEpochBytes := make([]byte, 8) - binary.BigEndian.PutUint64(currentEpochBytes, uint64(currentEpoch)) - - cnt := int64(1) - newEpoch := currentBlockTime / epochInterval - // still during the currentEpochID - if currentEpoch == newEpoch { - iter := trie.NewIterator(currentMintCntTrie.NodeIterator(currentEpochBytes)) - - // when current is not genesis, read last count from the MintCntTrie - if iter.Next() { - cntBytes := currentMintCntTrie.Get(append(currentEpochBytes, validator.Bytes()...)) - - // not the first time to mint - if cntBytes != nil { - cnt = int64(binary.BigEndian.Uint64(cntBytes)) + 1 - } - } - } - - newCntBytes := make([]byte, 8) - newEpochBytes := make([]byte, 8) - binary.BigEndian.PutUint64(newEpochBytes, uint64(newEpoch)) - binary.BigEndian.PutUint64(newCntBytes, uint64(cnt)) - dposContext.MintCntTrie().TryUpdate(append(newEpochBytes, validator.Bytes()...), newCntBytes) -} diff --git a/consensus/dpos/dpos_test.go b/consensus/dpos/dpos_test.go deleted file mode 100644 index 784fbbce9a..0000000000 --- a/consensus/dpos/dpos_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package dpos - -import ( - "testing" - - "encoding/binary" - - "github.com/meitu/go-ethereum/common" - "github.com/meitu/go-ethereum/core/types" - "github.com/meitu/go-ethereum/ethdb" - "github.com/meitu/go-ethereum/trie" - "github.com/stretchr/testify/assert" -) - -var ( - MockEpoch = []string{ - "0x44d1ce0b7cb3588bca96151fe1bc05af38f91b6e", - "0xa60a3886b552ff9992cfcd208ec1152079e046c2", - "0x4e080e49f62694554871e669aeb4ebe17c4a9670", - "0xb040353ec0f2c113d5639444f7253681aecda1f8", - "0x14432e15f21237013017fa6ee90fc99433dec82c", - "0x9f30d0e5c9c88cade54cd1adecf6bc2c7e0e5af6", - "0xd83b44a3719720ec54cdb9f54c0202de68f1ebcb", - "0x56cc452e450551b7b9cffe25084a069e8c1e9441", - "0xbcfcb3fa8250be4f2bf2b1e70e1da500c668377b", - "0x9d9667c71bb09d6ca7c3ed12bfe5e7be24e2ffe1", - "0xabde197e97398864ba74511f02832726edad5967", - "0x6f99d97a394fa7a623fdf84fdc7446b99c3cb335", - "0xf78b011e639ce6d8b76f97712118f3fe4a12dd95", - "0x8db3b6c801dddd624d6ddc2088aa64b5a2493661", - "0x751b484bd5296f8d267a8537d33f25a848f7f7af", - "0x646ba1fa42eb940aac67103a71e9a908ef484ec3", - "0x34d4a8d9f6b53a8f5e674516cb8ad66c843b2801", - "0x5b76fff970bf8a351c1c9ebfb5e5a9493e956ddd", - "0x8da3c5aedaf106c61cfee6d8483e1f255fdd60c0", - "0x2cdbe87a1bd7ee60dd6fe97f7b2d1efbacd5d95d", - "0x743415d0e979dc6e426bc8189e40beb65bf5ac1d", - } -) - -func mockNewDposContext(db ethdb.Database) *types.DposContext { - dposContext, err := types.NewDposContextFromProto(db, &types.DposContextProto{}) - if err != nil { - return nil - } - delegator := []byte{} - candidate := []byte{} - addresses := []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - addresses = append(addresses, common.HexToAddress(MockEpoch[i])) - } - dposContext.SetValidators(addresses) - for j := 0; j < len(MockEpoch); j++ { - delegator = common.HexToAddress(MockEpoch[j]).Bytes() - candidate = common.HexToAddress(MockEpoch[j]).Bytes() - dposContext.DelegateTrie().TryUpdate(append(candidate, delegator...), candidate) - dposContext.CandidateTrie().TryUpdate(candidate, candidate) - dposContext.VoteTrie().TryUpdate(candidate, candidate) - } - return dposContext -} - -func setMintCntTrie(epochID int64, candidate common.Address, mintCntTrie *trie.Trie, count int64) { - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, uint64(epochID)) - cntBytes := make([]byte, 8) - binary.BigEndian.PutUint64(cntBytes, uint64(count)) - mintCntTrie.TryUpdate(append(key, candidate.Bytes()...), cntBytes) -} - -func getMintCnt(epochID int64, candidate common.Address, mintCntTrie *trie.Trie) int64 { - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, uint64(epochID)) - cntBytes := mintCntTrie.Get(append(key, candidate.Bytes()...)) - if cntBytes == nil { - return 0 - } else { - return int64(binary.BigEndian.Uint64(cntBytes)) - } -} - -func TestUpdateMintCnt(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - dposContext := mockNewDposContext(db) - - // new block still in the same epoch with current block, but newMiner is the first time to mint in the epoch - lastTime := int64(epochInterval) - - miner := common.HexToAddress("0xa60a3886b552ff9992cfcd208ec1152079e046c2") - blockTime := int64(epochInterval + blockInterval) - - beforeUpdateCnt := getMintCnt(blockTime/epochInterval, miner, dposContext.MintCntTrie()) - updateMintCnt(lastTime, blockTime, miner, dposContext) - afterUpdateCnt := getMintCnt(blockTime/epochInterval, miner, dposContext.MintCntTrie()) - assert.Equal(t, int64(0), beforeUpdateCnt) - assert.Equal(t, int64(1), afterUpdateCnt) - - // new block still in the same epoch with current block, and newMiner has mint block before in the epoch - setMintCntTrie(blockTime/epochInterval, miner, dposContext.MintCntTrie(), int64(1)) - - blockTime = epochInterval + blockInterval*4 - - // currentBlock has recorded the count for the newMiner before UpdateMintCnt - beforeUpdateCnt = getMintCnt(blockTime/epochInterval, miner, dposContext.MintCntTrie()) - updateMintCnt(lastTime, blockTime, miner, dposContext) - afterUpdateCnt = getMintCnt(blockTime/epochInterval, miner, dposContext.MintCntTrie()) - assert.Equal(t, int64(1), beforeUpdateCnt) - assert.Equal(t, int64(2), afterUpdateCnt) - - // new block come to a new epoch - blockTime = epochInterval * 2 - - beforeUpdateCnt = getMintCnt(blockTime/epochInterval, miner, dposContext.MintCntTrie()) - updateMintCnt(lastTime, blockTime, miner, dposContext) - afterUpdateCnt = getMintCnt(blockTime/epochInterval, miner, dposContext.MintCntTrie()) - assert.Equal(t, int64(0), beforeUpdateCnt) - assert.Equal(t, int64(1), afterUpdateCnt) -} diff --git a/consensus/dpos/epoch_context.go b/consensus/dpos/epoch_context.go deleted file mode 100644 index 03cf821604..0000000000 --- a/consensus/dpos/epoch_context.go +++ /dev/null @@ -1,221 +0,0 @@ -package dpos - -import ( - "encoding/binary" - "errors" - "fmt" - "math/big" - "math/rand" - "sort" - - "github.com/meitu/go-ethereum/common" - "github.com/meitu/go-ethereum/core/state" - "github.com/meitu/go-ethereum/core/types" - "github.com/meitu/go-ethereum/crypto" - "github.com/meitu/go-ethereum/log" - "github.com/meitu/go-ethereum/trie" -) - -type EpochContext struct { - TimeStamp int64 - DposContext *types.DposContext - statedb *state.StateDB -} - -// countVotes -func (ec *EpochContext) countVotes() (votes map[common.Address]*big.Int, err error) { - votes = map[common.Address]*big.Int{} - delegateTrie := ec.DposContext.DelegateTrie() - candidateTrie := ec.DposContext.CandidateTrie() - statedb := ec.statedb - - iterCandidate := trie.NewIterator(candidateTrie.NodeIterator(nil)) - existCandidate := iterCandidate.Next() - if !existCandidate { - return votes, errors.New("no candidates") - } - for existCandidate { - candidate := iterCandidate.Value - candidateAddr := common.BytesToAddress(candidate) - delegateIterator := trie.NewIterator(delegateTrie.PrefixIterator(candidate)) - existDelegator := delegateIterator.Next() - if !existDelegator { - votes[candidateAddr] = new(big.Int) - existCandidate = iterCandidate.Next() - continue - } - for existDelegator { - delegator := delegateIterator.Value - score, ok := votes[candidateAddr] - if !ok { - score = new(big.Int) - } - delegatorAddr := common.BytesToAddress(delegator) - weight := statedb.GetBalance(delegatorAddr) - score.Add(score, weight) - votes[candidateAddr] = score - existDelegator = delegateIterator.Next() - } - existCandidate = iterCandidate.Next() - } - return votes, nil -} - -func (ec *EpochContext) kickoutValidator(epoch int64) error { - validators, err := ec.DposContext.GetValidators() - if err != nil { - return fmt.Errorf("failed to get validator: %s", err) - } - if len(validators) == 0 { - return errors.New("no validator could be kickout") - } - - epochDuration := epochInterval - // First epoch duration may lt epoch interval, - // while the first block time wouldn't always align with epoch interval, - // so caculate the first epoch duartion with first block time instead of epoch interval, - // prevent the validators were kickout incorrectly. - if ec.TimeStamp-timeOfFirstBlock < epochInterval { - epochDuration = ec.TimeStamp - timeOfFirstBlock - } - - needKickoutValidators := sortableAddresses{} - for _, validator := range validators { - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, uint64(epoch)) - key = append(key, validator.Bytes()...) - cnt := int64(0) - if cntBytes := ec.DposContext.MintCntTrie().Get(key); cntBytes != nil { - cnt = int64(binary.BigEndian.Uint64(cntBytes)) - } - if cnt < epochDuration/blockInterval/ maxValidatorSize /2 { - // not active validators need kickout - needKickoutValidators = append(needKickoutValidators, &sortableAddress{validator, big.NewInt(cnt)}) - } - } - // no validators need kickout - needKickoutValidatorCnt := len(needKickoutValidators) - if needKickoutValidatorCnt <= 0 { - return nil - } - sort.Sort(sort.Reverse(needKickoutValidators)) - - candidateCount := 0 - iter := trie.NewIterator(ec.DposContext.CandidateTrie().NodeIterator(nil)) - for iter.Next() { - candidateCount++ - if candidateCount >= needKickoutValidatorCnt+safeSize { - break - } - } - - for i, validator := range needKickoutValidators { - // ensure candidate count greater than or equal to safeSize - if candidateCount <= safeSize { - log.Info("No more candidate can be kickout", "prevEpochID", epoch, "candidateCount", candidateCount, "needKickoutCount", len(needKickoutValidators)-i) - return nil - } - - if err := ec.DposContext.KickoutCandidate(validator.address); err != nil { - return err - } - // if kickout success, candidateCount minus 1 - candidateCount-- - log.Info("Kickout candidate", "prevEpochID", epoch, "candidate", validator.address.String(), "mintCnt", validator.weight.String()) - } - return nil -} - -func (ec *EpochContext) lookupValidator(now int64) (validator common.Address, err error) { - validator = common.Address{} - offset := now % epochInterval - if offset%blockInterval != 0 { - return common.Address{}, ErrInvalidMintBlockTime - } - offset /= blockInterval - - validators, err := ec.DposContext.GetValidators() - if err != nil { - return common.Address{}, err - } - validatorSize := len(validators) - if validatorSize == 0 { - return common.Address{}, errors.New("failed to lookup validator") - } - offset %= int64(validatorSize) - return validators[offset], nil -} - -func (ec *EpochContext) tryElect(genesis, parent *types.Header) error { - genesisEpoch := genesis.Time.Int64() / epochInterval - prevEpoch := parent.Time.Int64() / epochInterval - currentEpoch := ec.TimeStamp / epochInterval - - prevEpochIsGenesis := prevEpoch == genesisEpoch - if prevEpochIsGenesis && prevEpoch < currentEpoch { - prevEpoch = currentEpoch - 1 - } - - prevEpochBytes := make([]byte, 8) - binary.BigEndian.PutUint64(prevEpochBytes, uint64(prevEpoch)) - iter := trie.NewIterator(ec.DposContext.MintCntTrie().PrefixIterator(prevEpochBytes)) - for i := prevEpoch; i < currentEpoch; i++ { - // if prevEpoch is not genesis, kickout not active candidate - if !prevEpochIsGenesis && iter.Next() { - if err := ec.kickoutValidator(prevEpoch); err != nil { - return err - } - } - votes, err := ec.countVotes() - if err != nil { - return err - } - candidates := sortableAddresses{} - for candidate, cnt := range votes { - candidates = append(candidates, &sortableAddress{candidate, cnt}) - } - if len(candidates) < safeSize { - return errors.New("too few candidates") - } - sort.Sort(candidates) - if len(candidates) > maxValidatorSize { - candidates = candidates[:maxValidatorSize] - } - - // shuffle candidates - seed := int64(binary.LittleEndian.Uint32(crypto.Keccak512(parent.Hash().Bytes()))) + i - r := rand.New(rand.NewSource(seed)) - for i := len(candidates) - 1; i > 0; i-- { - j := int(r.Int31n(int32(i + 1))) - candidates[i], candidates[j] = candidates[j], candidates[i] - } - sortedValidators := make([]common.Address, 0) - for _, candidate := range candidates { - sortedValidators = append(sortedValidators, candidate.address) - } - - epochTrie, _ := types.NewEpochTrie(common.Hash{}, ec.DposContext.DB()) - ec.DposContext.SetEpoch(epochTrie) - ec.DposContext.SetValidators(sortedValidators) - log.Info("Come to new epoch", "prevEpoch", i, "nextEpoch", i+1) - } - return nil -} - -type sortableAddress struct { - address common.Address - weight *big.Int -} -type sortableAddresses []*sortableAddress - -func (p sortableAddresses) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p sortableAddresses) Len() int { return len(p) } -func (p sortableAddresses) Less(i, j int) bool { - if p[i].weight.Cmp(p[j].weight) < 0 { - return false - } else if p[i].weight.Cmp(p[j].weight) > 0 { - return true - } else { - return p[i].address.String() < p[j].address.String() - } -} diff --git a/consensus/dpos/epoch_context_test.go b/consensus/dpos/epoch_context_test.go deleted file mode 100644 index 8a2fab6589..0000000000 --- a/consensus/dpos/epoch_context_test.go +++ /dev/null @@ -1,359 +0,0 @@ -package dpos - -import ( - "math/big" - "strconv" - "strings" - "testing" - - "github.com/meitu/go-ethereum/common" - "github.com/meitu/go-ethereum/core/state" - "github.com/meitu/go-ethereum/core/types" - "github.com/meitu/go-ethereum/ethdb" - "github.com/meitu/go-ethereum/trie" - - "github.com/stretchr/testify/assert" -) - -func TestEpochContextCountVotes(t *testing.T) { - voteMap := map[common.Address][]common.Address{ - common.HexToAddress("0x44d1ce0b7cb3588bca96151fe1bc05af38f91b6e"): { - common.HexToAddress("0xb040353ec0f2c113d5639444f7253681aecda1f8"), - }, - common.HexToAddress("0xa60a3886b552ff9992cfcd208ec1152079e046c2"): { - common.HexToAddress("0x14432e15f21237013017fa6ee90fc99433dec82c"), - common.HexToAddress("0x9f30d0e5c9c88cade54cd1adecf6bc2c7e0e5af6"), - }, - common.HexToAddress("0x4e080e49f62694554871e669aeb4ebe17c4a9670"): { - common.HexToAddress("0xd83b44a3719720ec54cdb9f54c0202de68f1ebcb"), - common.HexToAddress("0x56cc452e450551b7b9cffe25084a069e8c1e9441"), - common.HexToAddress("0xbcfcb3fa8250be4f2bf2b1e70e1da500c668377b"), - }, - common.HexToAddress("0x9d9667c71bb09d6ca7c3ed12bfe5e7be24e2ffe1"): {}, - } - balance := int64(5) - db, _ := ethdb.NewMemDatabase() - stateDB, _ := state.New(common.Hash{}, state.NewDatabase(db)) - dposContext, err := types.NewDposContext(db) - assert.Nil(t, err) - - epochContext := &EpochContext{ - DposContext: dposContext, - statedb: stateDB, - } - _, err = epochContext.countVotes() - assert.NotNil(t, err) - - for candidate, electors := range voteMap { - assert.Nil(t, dposContext.BecomeCandidate(candidate)) - for _, elector := range electors { - stateDB.SetBalance(elector, big.NewInt(balance)) - assert.Nil(t, dposContext.Delegate(elector, candidate)) - } - } - result, err := epochContext.countVotes() - assert.Nil(t, err) - assert.Equal(t, len(voteMap), len(result)) - for candidate, electors := range voteMap { - voteCount, ok := result[candidate] - assert.True(t, ok) - assert.Equal(t, balance*int64(len(electors)), voteCount.Int64()) - } -} - -func TestLookupValidator(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - dposCtx, _ := types.NewDposContext(db) - mockEpochContext := &EpochContext{ - DposContext: dposCtx, - } - validators := []common.Address{ - common.StringToAddress("addr1"), - common.StringToAddress("addr2"), - common.StringToAddress("addr3"), - } - mockEpochContext.DposContext.SetValidators(validators) - for i, expected := range validators { - got, _ := mockEpochContext.lookupValidator(int64(i) * blockInterval) - if got != expected { - t.Errorf("Failed to test lookup validator, %s was expected but got %s", expected.Str(), got.Str()) - } - } - _, err := mockEpochContext.lookupValidator(blockInterval - 1) - if err != ErrInvalidMintBlockTime { - t.Errorf("Failed to test lookup validator. err '%v' was expected but got '%v'", ErrInvalidMintBlockTime, err) - } -} - -func TestEpochContextKickoutValidator(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - stateDB, _ := state.New(common.Hash{}, state.NewDatabase(db)) - dposContext, err := types.NewDposContext(db) - assert.Nil(t, err) - epochContext := &EpochContext{ - TimeStamp: epochInterval, - DposContext: dposContext, - statedb: stateDB, - } - atLeastMintCnt := epochInterval / blockInterval / maxValidatorSize / 2 - testEpoch := int64(1) - - // no validator can be kickout, because all validators mint enough block at least - validators := []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - validator := common.StringToAddress("addr" + strconv.Itoa(i)) - validators = append(validators, validator) - assert.Nil(t, dposContext.BecomeCandidate(validator)) - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt) - } - assert.Nil(t, dposContext.SetValidators(validators)) - assert.Nil(t, dposContext.BecomeCandidate(common.StringToAddress("addr"))) - assert.Nil(t, epochContext.kickoutValidator(testEpoch)) - candidateMap := getCandidates(dposContext.CandidateTrie()) - assert.Equal(t, maxValidatorSize +1, len(candidateMap)) - - // atLeast a safeSize count candidate will reserve - dposContext, err = types.NewDposContext(db) - assert.Nil(t, err) - epochContext = &EpochContext{ - TimeStamp: epochInterval, - DposContext: dposContext, - statedb: stateDB, - } - validators = []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - validator := common.StringToAddress("addr" + strconv.Itoa(i)) - validators = append(validators, validator) - assert.Nil(t, dposContext.BecomeCandidate(validator)) - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt-int64(i)-1) - } - assert.Nil(t, dposContext.SetValidators(validators)) - assert.Nil(t, epochContext.kickoutValidator(testEpoch)) - candidateMap = getCandidates(dposContext.CandidateTrie()) - assert.Equal(t, safeSize, len(candidateMap)) - for i := maxValidatorSize - 1; i >= safeSize; i-- { - assert.False(t, candidateMap[common.StringToAddress("addr"+strconv.Itoa(i))]) - } - - // all validator will be kickout, because all validators didn't mint enough block at least - dposContext, err = types.NewDposContext(db) - assert.Nil(t, err) - epochContext = &EpochContext{ - TimeStamp: epochInterval, - DposContext: dposContext, - statedb: stateDB, - } - validators = []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - validator := common.StringToAddress("addr" + strconv.Itoa(i)) - validators = append(validators, validator) - assert.Nil(t, dposContext.BecomeCandidate(validator)) - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt-1) - } - for i := maxValidatorSize; i < maxValidatorSize *2; i++ { - candidate := common.StringToAddress("addr" + strconv.Itoa(i)) - assert.Nil(t, dposContext.BecomeCandidate(candidate)) - } - assert.Nil(t, dposContext.SetValidators(validators)) - assert.Nil(t, epochContext.kickoutValidator(testEpoch)) - candidateMap = getCandidates(dposContext.CandidateTrie()) - assert.Equal(t, maxValidatorSize, len(candidateMap)) - - // only one validator mint count is not enough - dposContext, err = types.NewDposContext(db) - assert.Nil(t, err) - epochContext = &EpochContext{ - TimeStamp: epochInterval, - DposContext: dposContext, - statedb: stateDB, - } - validators = []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - validator := common.StringToAddress("addr" + strconv.Itoa(i)) - validators = append(validators, validator) - assert.Nil(t, dposContext.BecomeCandidate(validator)) - if i == 0 { - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt-1) - } else { - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt) - } - } - assert.Nil(t, dposContext.BecomeCandidate(common.StringToAddress("addr"))) - assert.Nil(t, dposContext.SetValidators(validators)) - assert.Nil(t, epochContext.kickoutValidator(testEpoch)) - candidateMap = getCandidates(dposContext.CandidateTrie()) - assert.Equal(t, maxValidatorSize, len(candidateMap)) - assert.False(t, candidateMap[common.StringToAddress("addr"+strconv.Itoa(0))]) - - // epochTime is not complete, all validators mint enough block at least - dposContext, err = types.NewDposContext(db) - assert.Nil(t, err) - epochContext = &EpochContext{ - TimeStamp: epochInterval / 2, - DposContext: dposContext, - statedb: stateDB, - } - validators = []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - validator := common.StringToAddress("addr" + strconv.Itoa(i)) - validators = append(validators, validator) - assert.Nil(t, dposContext.BecomeCandidate(validator)) - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt/2) - } - for i := maxValidatorSize; i < maxValidatorSize *2; i++ { - candidate := common.StringToAddress("addr" + strconv.Itoa(i)) - assert.Nil(t, dposContext.BecomeCandidate(candidate)) - } - assert.Nil(t, dposContext.SetValidators(validators)) - assert.Nil(t, epochContext.kickoutValidator(testEpoch)) - candidateMap = getCandidates(dposContext.CandidateTrie()) - assert.Equal(t, maxValidatorSize *2, len(candidateMap)) - - // epochTime is not complete, all validators didn't mint enough block at least - dposContext, err = types.NewDposContext(db) - assert.Nil(t, err) - epochContext = &EpochContext{ - TimeStamp: epochInterval / 2, - DposContext: dposContext, - statedb: stateDB, - } - validators = []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - validator := common.StringToAddress("addr" + strconv.Itoa(i)) - validators = append(validators, validator) - assert.Nil(t, dposContext.BecomeCandidate(validator)) - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt/2-1) - } - for i := maxValidatorSize; i < maxValidatorSize *2; i++ { - candidate := common.StringToAddress("addr" + strconv.Itoa(i)) - assert.Nil(t, dposContext.BecomeCandidate(candidate)) - } - assert.Nil(t, dposContext.SetValidators(validators)) - assert.Nil(t, epochContext.kickoutValidator(testEpoch)) - candidateMap = getCandidates(dposContext.CandidateTrie()) - assert.Equal(t, maxValidatorSize, len(candidateMap)) - - dposContext, err = types.NewDposContext(db) - assert.Nil(t, err) - epochContext = &EpochContext{ - TimeStamp: epochInterval / 2, - DposContext: dposContext, - statedb: stateDB, - } - assert.NotNil(t, epochContext.kickoutValidator(testEpoch)) - dposContext.SetValidators([]common.Address{}) - assert.NotNil(t, epochContext.kickoutValidator(testEpoch)) -} - -func setTestMintCnt(dposContext *types.DposContext, epoch int64, validator common.Address, count int64) { - for i := int64(0); i < count; i++ { - updateMintCnt(epoch*epochInterval, epoch*epochInterval+blockInterval, validator, dposContext) - } -} - -func getCandidates(candidateTrie *trie.Trie) map[common.Address]bool { - candidateMap := map[common.Address]bool{} - iter := trie.NewIterator(candidateTrie.NodeIterator(nil)) - for iter.Next() { - candidateMap[common.BytesToAddress(iter.Value)] = true - } - return candidateMap -} - -func TestEpochContextTryElect(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - stateDB, _ := state.New(common.Hash{}, state.NewDatabase(db)) - dposContext, err := types.NewDposContext(db) - assert.Nil(t, err) - epochContext := &EpochContext{ - TimeStamp: epochInterval, - DposContext: dposContext, - statedb: stateDB, - } - atLeastMintCnt := epochInterval / blockInterval / maxValidatorSize / 2 - testEpoch := int64(1) - validators := []common.Address{} - for i := 0; i < maxValidatorSize; i++ { - validator := common.StringToAddress("addr" + strconv.Itoa(i)) - validators = append(validators, validator) - assert.Nil(t, dposContext.BecomeCandidate(validator)) - assert.Nil(t, dposContext.Delegate(validator, validator)) - stateDB.SetBalance(validator, big.NewInt(1)) - setTestMintCnt(dposContext, testEpoch, validator, atLeastMintCnt-1) - } - dposContext.BecomeCandidate(common.StringToAddress("more")) - assert.Nil(t, dposContext.SetValidators(validators)) - - // genesisEpoch == parentEpoch do not kickout - genesis := &types.Header{ - Time: big.NewInt(0), - } - parent := &types.Header{ - Time: big.NewInt(epochInterval - blockInterval), - } - oldHash := dposContext.EpochTrie().Hash() - assert.Nil(t, epochContext.tryElect(genesis, parent)) - result, err := dposContext.GetValidators() - assert.Nil(t, err) - assert.Equal(t, maxValidatorSize, len(result)) - for _, validator := range result { - assert.True(t, strings.Contains(validator.Str(), "addr")) - } - assert.NotEqual(t, oldHash, dposContext.EpochTrie().Hash()) - - // genesisEpoch != parentEpoch and have none mintCnt do not kickout - genesis = &types.Header{ - Time: big.NewInt(-epochInterval), - } - parent = &types.Header{ - Difficulty: big.NewInt(1), - Time: big.NewInt(epochInterval - blockInterval), - } - epochContext.TimeStamp = epochInterval - oldHash = dposContext.EpochTrie().Hash() - assert.Nil(t, epochContext.tryElect(genesis, parent)) - result, err = dposContext.GetValidators() - assert.Nil(t, err) - assert.Equal(t, maxValidatorSize, len(result)) - for _, validator := range result { - assert.True(t, strings.Contains(validator.Str(), "addr")) - } - assert.NotEqual(t, oldHash, dposContext.EpochTrie().Hash()) - - // genesisEpoch != parentEpoch kickout - genesis = &types.Header{ - Time: big.NewInt(0), - } - parent = &types.Header{ - Time: big.NewInt(epochInterval*2 - blockInterval), - } - epochContext.TimeStamp = epochInterval * 2 - oldHash = dposContext.EpochTrie().Hash() - assert.Nil(t, epochContext.tryElect(genesis, parent)) - result, err = dposContext.GetValidators() - assert.Nil(t, err) - assert.Equal(t, safeSize, len(result)) - moreCnt := 0 - for _, validator := range result { - if strings.Contains(validator.Str(), "more") { - moreCnt++ - } - } - assert.Equal(t, 1, moreCnt) - assert.NotEqual(t, oldHash, dposContext.EpochTrie().Hash()) - - // parentEpoch == currentEpoch do not elect - genesis = &types.Header{ - Time: big.NewInt(0), - } - parent = &types.Header{ - Time: big.NewInt(epochInterval), - } - epochContext.TimeStamp = epochInterval + blockInterval - oldHash = dposContext.EpochTrie().Hash() - assert.Nil(t, epochContext.tryElect(genesis, parent)) - result, err = dposContext.GetValidators() - assert.Nil(t, err) - assert.Equal(t, safeSize, len(result)) - assert.Equal(t, oldHash, dposContext.EpochTrie().Hash()) -} diff --git a/consensus/lcp/api.go b/consensus/lcp/api.go new file mode 100644 index 0000000000..37110bfe55 --- /dev/null +++ b/consensus/lcp/api.go @@ -0,0 +1,120 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + + +package lcp + +import ( + "github.com/pavelkrolevets/go-ethereum/common" + "github.com/pavelkrolevets/go-ethereum/consensus" + "github.com/pavelkrolevets/go-ethereum/core/types" + "github.com/pavelkrolevets/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 + lcp *LCP +} + +// 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.lcp.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.clique.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.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.signers(), nil +} + +// GetSignersAtHash retrieves the list of authorized signers at the specified 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.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.signers(), nil +} + +// Proposals returns the current proposals the node tries to uphold and vote on. +func (api *API) Proposals() map[common.Address]bool { + api.clique.lock.RLock() + defer api.clique.lock.RUnlock() + + proposals := make(map[common.Address]bool) + for address, auth := range api.clique.proposals { + proposals[address] = auth + } + return proposals +} + +// Propose injects a new authorization proposal that the signer will attempt to +// push through. +func (api *API) Propose(address common.Address, auth bool) { + api.clique.lock.Lock() + defer api.clique.lock.Unlock() + + api.clique.proposals[address] = auth +} + +// Discard drops a currently running proposal, stopping the signer from casting +// further votes (either for or against). +func (api *API) Discard(address common.Address) { + api.clique.lock.Lock() + defer api.clique.lock.Unlock() + + delete(api.clique.proposals, address) +} \ No newline at end of file diff --git a/consensus/lcp/epoch_cotext.go b/consensus/lcp/epoch_cotext.go new file mode 100644 index 0000000000..b737ee2ca8 --- /dev/null +++ b/consensus/lcp/epoch_cotext.go @@ -0,0 +1,314 @@ + +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package lcp + +import ( +"bytes" +"encoding/json" +"sort" + +"github.com/pavelkrolevets/go-ethereum/common" +"github.com/pavelkrolevets/go-ethereum/core/types" +"github.com/pavelkrolevets/go-ethereum/ethdb" +"github.com/pavelkrolevets/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.CliqueConfig // 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 []*Vote `json:"votes"` // List of votes cast in chronological order + Tally map[common.Address]Tally `json:"tally"` // Current vote tally to avoid recalculating +} + +// signers implements the sort interface to allow sorting a list of addresses +type signers []common.Address + +func (s signers) Len() int { return len(s) } +func (s signers) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 } +func (s signers) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// 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 { + 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]Tally), + } + for _, signer := range signers { + snap.Signers[signer] = struct{}{} + } + return snap +} + +// loadSnapshot loads an existing snapshot from the database. +func loadSnapshot(config *params.CliqueConfig, 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 + } + 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("clique-"), 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([]*Vote, len(s.Votes)), + Tally: make(map[common.Address]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] = 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]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 + } + 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, &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) signers() []common.Address { + sigs := make([]common.Address, 0, len(s.Signers)) + for sig := range s.Signers { + sigs = append(sigs, sig) + } + sort.Sort(signers(sigs)) + return sigs +} + +// 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.signers(), 0 + for offset < len(signers) && signers[offset] != signer { + offset++ + } + return (number % uint64(len(signers))) == uint64(offset) +} + diff --git a/consensus/lcp/lcp.go b/consensus/lcp/lcp.go new file mode 100644 index 0000000000..74364a4079 --- /dev/null +++ b/consensus/lcp/lcp.go @@ -0,0 +1,680 @@ +package lcp + + +import ( + "bytes" + "errors" + "math/big" + "math/rand" + "sync" + "time" + + "github.com/pavelkrolevets/go-ethereum/accounts" + "github.com/pavelkrolevets/go-ethereum/common" + "github.com/pavelkrolevets/go-ethereum/common/hexutil" + "github.com/pavelkrolevets/go-ethereum/consensus" + "github.com/pavelkrolevets/go-ethereum/consensus/misc" + "github.com/pavelkrolevets/go-ethereum/core/state" + "github.com/pavelkrolevets/go-ethereum/core/types" + "github.com/pavelkrolevets/go-ethereum/crypto" + "github.com/pavelkrolevets/go-ethereum/crypto/sha3" + "github.com/pavelkrolevets/go-ethereum/ethdb" + "github.com/pavelkrolevets/go-ethereum/log" + "github.com/pavelkrolevets/go-ethereum/params" + "github.com/pavelkrolevets/go-ethereum/rlp" + "github.com/pavelkrolevets/go-ethereum/rpc" + lru "github.com/hashicorp/golang-lru" +) + +const ( + 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 + wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers + maxValidatorSize = 1 + safeSize = 1 //maxValidatorSize*2/3 + 1 + consensusSize = 1 //maxValidatorSize*2/3 + 1 + +) + +// DPOS-PBFT protocol constants. +var ( + epochLength = uint64(30000) // 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 validator + nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a validator. + + 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") + + // 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") + + // 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") +) + +// 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 LCP +// 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, + header.LCPContext.Root(), + + }) + hasher.Sum(hash[:0]) + return hash +} + +// 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 +} + +// Clique is the proof-of-authority consensus engine proposed to support the +// Ethereum testnet following the Ropsten attacks. +type LCP struct { + config *params.LcpConfig // 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 + + proposals map[common.Address]bool // Current list of proposals we are pushing + + signer common.Address // Ethereum address of the signing key + signFn SignerFn // Signer function to authorize hashes with + lock sync.RWMutex // Protects the signer fields + stop chan bool +} + +// New creates a LCP DPOS consensus engine with the initial +// signers set to the ones provided by the user. +func New(config *params.LcpConfig, db ethdb.Database) *Clique { + // 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(inmemorySignatures) + + return &LCP{ + config: &conf, + db: db, + recents: recents, + signatures: signatures, + 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 *LCP) Author(header *types.Header) (common.Address, error) { + return ecrecover(header, c.signatures) +} + +// VerifyHeader checks whether a header conforms to the consensus rules. +func (c *LCP) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { + return c.verifyHeader(chain, header, nil) +} + +// 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 *Clique) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + results := make(chan error, len(headers)) + + go func() { + for i, header := range headers { + err := c.verifyHeader(chain, header, headers[:i]) + + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +// 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 *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { + if header.Number == nil { + return errUnknownBlock + } + number := header.Number.Uint64() + + // 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 PoA + if header.UncleHash != uncleHash { + return errInvalidUncleHash + } + // Ensure that the block's difficulty is meaningful (may not be correct at this point) + if number > 0 { + if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0) { + return errInvalidDifficulty + } + } + // 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) +} + +// 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 *Clique) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) 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 { + signers := make([]byte, len(snap.Signers)*common.AddressLength) + for i, signer := range snap.signers() { + copy(signers[i*common.AddressLength:], signer[:]) + } + extraSuffix := len(header.Extra) - extraSeal + if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) { + return errInvalidCheckpointSigners + } + } + // All basic checks passed, verify the seal and return + return c.verifySeal(chain, header, parents) +} + +// 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 + 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 + if number%checkpointInterval == 0 { + if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { + log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash) + snap = s + break + } + } + // If we're at block zero, make a snapshot + if number == 0 { + genesis := chain.GetHeaderByNumber(0) + if err := c.VerifyHeader(chain, genesis, false); 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%checkpointInterval == 0 && len(headers) > 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 *Clique) 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 *Clique) VerifySeal(chain consensus.ChainReader, header *types.Header) error { + return c.verifySeal(chain, header, nil) +} + +// 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. +func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) 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 + signer, err := ecrecover(header, c.signatures) + if err != nil { + return err + } + if _, ok := snap.Signers[signer]; !ok { + 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(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 +} + +// Prepare implements consensus.Engine, preparing all the consensus fields of the +// header for running the transactions on top. +func (c *Clique) 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() + } + // Set the correct difficulty + header.Difficulty = CalcDifficulty(snap, c.signer) + + // 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] + + if number%c.config.Epoch == 0 { + for _, signer := range snap.signers() { + header.Extra = append(header.Extra, signer[:]...) + } + } + 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 + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + 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 +} + +// 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) { + // 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) + + // 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 *Clique) Authorize(signer common.Address, signFn 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 *Clique) 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 + } + if _, authorized := snap.Signers[signer]; !authorized { + 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(snap.Signers)/2 + 1); number < limit || seen > number-limit { + log.Info("Signed recently, must wait for others") + <-stop + return nil, nil + } + } + } + // Sweet, the protocol permits us to sign the block, wait for our time + 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(snap.Signers)/2+1) * wiggleTime + delay += time.Duration(rand.Int63n(int64(wiggle))) + + log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) + } + log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) + + select { + case <-stop: + return nil, nil + case <-time.After(delay): + } + // 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 *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { + snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil) + if err != nil { + return nil + } + return CalcDifficulty(snap, c.signer) +} + +// 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 CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int { + if snap.inturn(snap.Number+1, signer) { + return new(big.Int).Set(diffInTurn) + } + return new(big.Int).Set(diffNoTurn) +} + +// Close implements consensus.Engine. It's a noop for clique as there is are no background threads. +func (c *Clique) Close() error { + return nil +} + +// APIs implements consensus.Engine, returning the user facing RPC API to allow +// controlling the signer voting. +func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API { + return []rpc.API{{ + Namespace: "clique", + Version: "1.0", + Service: &API{chain: chain, clique: c}, + Public: false, + }} +} + diff --git a/core/chain_makers.go b/core/chain_makers.go index ee37903c63..a540928b89 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -165,6 +165,7 @@ func (b *BlockGen) OffsetTime(seconds int64) { // values. Inserting them into BlockChain requires use of FakePow or // a similar non-validating proof of work implementation. func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) { + // Force LCP configuration if everything is empty if config == nil { config = params.TestChainConfig } diff --git a/core/genesis.go b/core/genesis.go index bd3e048ae3..8afddde14c 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -152,7 +152,7 @@ func (e *GenesisMismatchError) Error() string { // The returned chain configuration is never nil. func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { if genesis != nil && genesis.Config == nil { - return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig + return params.LcpChainConfig, common.Hash{}, errGenesisNoConfig } // Just commit the new block if there is no stored genesis block. @@ -209,12 +209,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { switch { case g != nil: return g.Config - case ghash == params.MainnetGenesisHash: - return params.MainnetChainConfig - case ghash == params.TestnetGenesisHash: - return params.TestnetChainConfig default: - return params.AllEthashProtocolChanges + return params.LcpChainConfig } } @@ -234,6 +230,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { } } root := statedb.IntermediateRoot(false) + // LCP context + LCPContext := initGenesisLCPContext(g, db) + LCPContextProto := LCPContext.ToProto() head := &types.Header{ Number: new(big.Int).SetUint64(g.Number), Nonce: types.EncodeNonce(g.Nonce), @@ -246,6 +245,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { MixDigest: g.Mixhash, Coinbase: g.Coinbase, Root: root, + LCPContext: LCPContextProto, } if g.GasLimit == 0 { head.GasLimit = params.GenesisGasLimit @@ -256,6 +256,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { statedb.Commit(false) statedb.Database().TrieDB().Commit(root, true) + block := types.NewBlock(head, nil, nil, nil) + block.LCPContext = LCPContext + return types.NewBlock(head, nil, nil, nil) } @@ -263,6 +266,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { // The block is committed as the canonical head block. func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { block := g.ToBlock(db) + // LCP context + if _, err := block.LCPContext.CommitTo(db); err != nil { + return nil, err + } if block.Number().Sign() != 0 { return nil, fmt.Errorf("can't commit genesis block with number > 0") } @@ -275,7 +282,7 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { config := g.Config if config == nil { - config = params.AllEthashProtocolChanges + config = params.LcpChainConfig } rawdb.WriteChainConfig(db, block.Hash(), config) return block, nil @@ -300,7 +307,7 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big // DefaultGenesisBlock returns the Ethereum main net genesis block. func DefaultGenesisBlock() *Genesis { return &Genesis{ - Config: params.MainnetChainConfig, + Config: params.LcpChainConfig, Nonce: 66, ExtraData: hexutil.MustDecode("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"), GasLimit: 5000, @@ -309,57 +316,6 @@ func DefaultGenesisBlock() *Genesis { } } -// DefaultTestnetGenesisBlock returns the Ropsten network genesis block. -func DefaultTestnetGenesisBlock() *Genesis { - return &Genesis{ - Config: params.TestnetChainConfig, - Nonce: 66, - ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"), - GasLimit: 16777216, - Difficulty: big.NewInt(1048576), - Alloc: decodePrealloc(testnetAllocData), - } -} - -// DefaultRinkebyGenesisBlock returns the Rinkeby network genesis block. -func DefaultRinkebyGenesisBlock() *Genesis { - return &Genesis{ - Config: params.RinkebyChainConfig, - Timestamp: 1492009146, - ExtraData: hexutil.MustDecode("0x52657370656374206d7920617574686f7269746168207e452e436172746d616e42eb768f2244c8811c63729a21a3569731535f067ffc57839b00206d1ad20c69a1981b489f772031b279182d99e65703f0076e4812653aab85fca0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), - GasLimit: 4700000, - Difficulty: big.NewInt(1), - Alloc: decodePrealloc(rinkebyAllocData), - } -} - -// DeveloperGenesisBlock returns the 'geth --dev' genesis block. Note, this must -// 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 - - // Assemble and return the genesis with the precompiles and faucet pre-funded - return &Genesis{ - Config: &config, - ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, 65)...), - GasLimit: 6283185, - Difficulty: big.NewInt(1), - Alloc: map[common.Address]GenesisAccount{ - common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover - common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 - common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD - common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity - common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp - common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd - common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul - common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing - faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))}, - }, - } -} - func decodePrealloc(data string) GenesisAlloc { var p []struct{ Addr, Balance *big.Int } if err := rlp.NewStream(strings.NewReader(data), 0).Decode(&p); err != nil { @@ -371,3 +327,27 @@ func decodePrealloc(data string) GenesisAlloc { } return ga } + +func initGenesisLCPContext(g *Genesis, db ethdb.Database) *types.LCPContext { + dc, err := types.NewLCPContextFromProto(db, &types.LCPContextProto{}) + if err != nil { + return nil + } + if g.Config != nil && g.Config.LCP != nil && g.Config.LCP.Validators != nil { + dc.SetValidators(g.Config.LCP.Validators) + for _, validator := range g.Config.LCP.Validators { + dc.DelegateTrie().TryUpdate(append(validator.Bytes(), validator.Bytes()...), validator.Bytes()) + dc.CandidateTrie().TryUpdate(validator.Bytes(), validator.Bytes()) + } + } + if g.Config != nil && g.Config.LCP != nil && g.Config.LCP.Period != 0 { + dc.SetPeriod(g.Config.LCP.Period) + } + if g.Config != nil && g.Config.LCP != nil && g.Config.LCP.MaxValidators != 0 { + dc.SetMaxValidators(g.Config.LCP.MaxValidators) + } + if g.Config != nil && g.Config.LCP != nil && g.Config.LCP.Epoch != 0 { + dc.SetEpochInterval(g.Config.LCP.Epoch) + } + return dc +} diff --git a/core/types/block.go b/core/types/block.go index 606f73ff59..be36619c32 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -72,7 +72,7 @@ type Header struct { ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Validator common.Address `json:"validator" gencodec:"required"` - DposContext *DposContextProto `json:"dposContext" gencodec:"required"` + LCPContext *LCPContextProto `json:"LCPContext" gencodec:"required"` Coinbase common.Address `json:"miner" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` @@ -163,7 +163,7 @@ type Block struct { // inter-peer block relay. ReceivedAt time.Time ReceivedFrom interface{} - DposContext *DposContext + LCPContext *LCPContext } // DeprecatedTd is an old relic for extracting the TD of a block. It is in the diff --git a/core/types/dpos_context.go b/core/types/lcp_context.go similarity index 67% rename from core/types/dpos_context.go rename to core/types/lcp_context.go index bd6d1ef3cd..8f0fc4a0a3 100644 --- a/core/types/dpos_context.go +++ b/core/types/lcp_context.go @@ -5,21 +5,24 @@ import ( "errors" "fmt" - "github.com/pavelkrolevets/go-ethereum/common" - "github.com/pavelkrolevets/go-ethereum/crypto/sha3" - "github.com/pavelkrolevets/go-ethereum/ethdb" - "github.com/pavelkrolevets/go-ethereum/rlp" - "github.com/pavelkrolevets/go-ethereum/trie" + "github.com/meitu/go-ethereum/common" + "github.com/meitu/go-ethereum/crypto/sha3" + "github.com/meitu/go-ethereum/ethdb" + "github.com/meitu/go-ethereum/rlp" + "github.com/meitu/go-ethereum/trie" ) -type DposContext struct { +type LCPContext struct { epochTrie *trie.Trie delegateTrie *trie.Trie voteTrie *trie.Trie candidateTrie *trie.Trie mintCntTrie *trie.Trie + period uint64 + maxValidators uint64 + epochInterval uint64 - db trie.Database + db ethdb.Database } var ( @@ -30,27 +33,27 @@ var ( mintCntPrefix = []byte("mintCnt-") ) -func NewEpochTrie(root common.Hash, db trie.Database) (*trie.Trie, error) { +func NewEpochTrie(root common.Hash, db ethdb.Database) (*trie.Trie, error) { return trie.NewTrieWithPrefix(root, epochPrefix, db) } -func NewDelegateTrie(root common.Hash, db trie.Database) (*trie.Trie, error) { +func NewDelegateTrie(root common.Hash, db ethdb.Database) (*trie.Trie, error) { return trie.NewTrieWithPrefix(root, delegatePrefix, db) } -func NewVoteTrie(root common.Hash, db trie.Database) (*trie.Trie, error) { +func NewVoteTrie(root common.Hash, db ethdb.Database) (*trie.Trie, error) { return trie.NewTrieWithPrefix(root, votePrefix, db) } -func NewCandidateTrie(root common.Hash, db trie.Database) (*trie.Trie, error) { +func NewCandidateTrie(root common.Hash, db ethdb.Database) (*trie.Trie, error) { return trie.NewTrieWithPrefix(root, candidatePrefix, db) } -func NewMintCntTrie(root common.Hash, db trie.Database) (*trie.Trie, error) { +func NewMintCntTrie(root common.Hash, db ethdb.Database) (*trie.Trie, error) { return trie.NewTrieWithPrefix(root, mintCntPrefix, db) } -func NewDposContext(db trie.Database) (*DposContext, error) { +func NewLCPContext(db ethdb.Database) (*LCPContext, error) { epochTrie, err := NewEpochTrie(common.Hash{}, db) if err != nil { return nil, err @@ -71,7 +74,8 @@ func NewDposContext(db trie.Database) (*DposContext, error) { if err != nil { return nil, err } - return &DposContext{ + + return &LCPContext{ epochTrie: epochTrie, delegateTrie: delegateTrie, voteTrie: voteTrie, @@ -81,7 +85,7 @@ func NewDposContext(db trie.Database) (*DposContext, error) { }, nil } -func NewDposContextFromProto(db trie.Database, ctxProto *DposContextProto) (*DposContext, error) { +func NewLCPContextFromProto(db ethdb.Database, ctxProto *LCPContextProto) (*LCPContext, error) { epochTrie, err := NewEpochTrie(ctxProto.EpochHash, db) if err != nil { return nil, err @@ -102,7 +106,7 @@ func NewDposContextFromProto(db trie.Database, ctxProto *DposContextProto) (*Dpo if err != nil { return nil, err } - return &DposContext{ + return &LCPContext{ epochTrie: epochTrie, delegateTrie: delegateTrie, voteTrie: voteTrie, @@ -112,13 +116,13 @@ func NewDposContextFromProto(db trie.Database, ctxProto *DposContextProto) (*Dpo }, nil } -func (d *DposContext) Copy() *DposContext { +func (d *LCPContext) Copy() *LCPContext { epochTrie := *d.epochTrie delegateTrie := *d.delegateTrie voteTrie := *d.voteTrie candidateTrie := *d.candidateTrie mintCntTrie := *d.mintCntTrie - return &DposContext{ + return &LCPContext{ epochTrie: &epochTrie, delegateTrie: &delegateTrie, voteTrie: &voteTrie, @@ -127,22 +131,23 @@ func (d *DposContext) Copy() *DposContext { } } -func (d *DposContext) Root() (h common.Hash) { +func (d *LCPContext) Root() (h common.Hash) { hw := sha3.NewKeccak256() rlp.Encode(hw, d.epochTrie.Hash()) rlp.Encode(hw, d.delegateTrie.Hash()) rlp.Encode(hw, d.candidateTrie.Hash()) rlp.Encode(hw, d.voteTrie.Hash()) rlp.Encode(hw, d.mintCntTrie.Hash()) + hw.Sum(h[:0]) return h } -func (d *DposContext) Snapshot() *DposContext { +func (d *LCPContext) Snapshot() *LCPContext { return d.Copy() } -func (d *DposContext) RevertToSnapShot(snapshot *DposContext) { +func (d *LCPContext) RevertToSnapShot(snapshot *LCPContext) { d.epochTrie = snapshot.epochTrie d.delegateTrie = snapshot.delegateTrie d.candidateTrie = snapshot.candidateTrie @@ -150,7 +155,7 @@ func (d *DposContext) RevertToSnapShot(snapshot *DposContext) { d.mintCntTrie = snapshot.mintCntTrie } -func (d *DposContext) FromProto(dcp *DposContextProto) error { +func (d *LCPContext) FromProto(dcp *LCPContextProto) error { var err error d.epochTrie, err = NewEpochTrie(dcp.EpochHash, d.db) if err != nil { @@ -169,39 +174,57 @@ func (d *DposContext) FromProto(dcp *DposContextProto) error { return err } d.mintCntTrie, err = NewMintCntTrie(dcp.MintCntHash, d.db) + + d.period = dcp.period + d.epochInterval = dcp.epochInterval + d.maxValidators = dcp.maxValidators return err } -type DposContextProto struct { +type LCPContextProto struct { EpochHash common.Hash `json:"epochRoot" gencodec:"required"` DelegateHash common.Hash `json:"delegateRoot" gencodec:"required"` CandidateHash common.Hash `json:"candidateRoot" gencodec:"required"` VoteHash common.Hash `json:"voteRoot" gencodec:"required"` MintCntHash common.Hash `json:"mintCntRoot" gencodec:"required"` + period uint64 + maxValidators uint64 + epochInterval uint64 + + } -func (d *DposContext) ToProto() *DposContextProto { - return &DposContextProto{ +func (d *LCPContext) ToProto() *LCPContextProto { + return &LCPContextProto{ EpochHash: d.epochTrie.Hash(), DelegateHash: d.delegateTrie.Hash(), CandidateHash: d.candidateTrie.Hash(), VoteHash: d.voteTrie.Hash(), MintCntHash: d.mintCntTrie.Hash(), + period: d.period, + maxValidators: d.maxValidators, + epochInterval: d.epochInterval, } } -func (p *DposContextProto) Root() (h common.Hash) { +func (p *LCPContextProto) Root() (h common.Hash) { hw := sha3.NewKeccak256() rlp.Encode(hw, p.EpochHash) rlp.Encode(hw, p.DelegateHash) rlp.Encode(hw, p.CandidateHash) rlp.Encode(hw, p.VoteHash) rlp.Encode(hw, p.MintCntHash) + rlp.Encode(hw, p.period) + rlp.Encode(hw, p.epochInterval) + rlp.Encode(hw, p.maxValidators) + rlp.Encode(hw, p.period) + rlp.Encode(hw, p.maxValidators) + rlp.Encode(hw, p.epochInterval) hw.Sum(h[:0]) return h } -func (d *DposContext) KickoutCandidate(candidateAddr common.Address) error { +func (d *LCPContext) KickoutCandidate(candidateAddr common.Address) error { candidate := candidateAddr.Bytes() err := d.candidateTrie.TryDelete(candidate) if err != nil { @@ -237,12 +260,12 @@ func (d *DposContext) KickoutCandidate(candidateAddr common.Address) error { return nil } -func (d *DposContext) BecomeCandidate(candidateAddr common.Address) error { +func (d *LCPContext) BecomeCandidate(candidateAddr common.Address) error { candidate := candidateAddr.Bytes() return d.candidateTrie.TryUpdate(candidate, candidate) } -func (d *DposContext) Delegate(delegatorAddr, candidateAddr common.Address) error { +func (d *LCPContext) Delegate(delegatorAddr, candidateAddr common.Address) error { delegator, candidate := delegatorAddr.Bytes(), candidateAddr.Bytes() // the candidate must be candidate @@ -270,7 +293,7 @@ func (d *DposContext) Delegate(delegatorAddr, candidateAddr common.Address) erro return d.voteTrie.TryUpdate(delegator, candidate) } -func (d *DposContext) UnDelegate(delegatorAddr, candidateAddr common.Address) error { +func (d *LCPContext) UnDelegate(delegatorAddr, candidateAddr common.Address) error { delegator, candidate := delegatorAddr.Bytes(), candidateAddr.Bytes() // the candidate must be candidate @@ -296,7 +319,7 @@ func (d *DposContext) UnDelegate(delegatorAddr, candidateAddr common.Address) er return d.voteTrie.TryDelete(delegator) } -func (d *DposContext) CommitTo(dbw trie.DatabaseWriter) (*DposContextProto, error) { +func (d *LCPContext) CommitTo(dbw trie.DatabaseWriter) (*LCPContextProto, error) { epochRoot, err := d.epochTrie.CommitTo(dbw) if err != nil { return nil, err @@ -317,7 +340,7 @@ func (d *DposContext) CommitTo(dbw trie.DatabaseWriter) (*DposContextProto, erro if err != nil { return nil, err } - return &DposContextProto{ + return &LCPContextProto{ EpochHash: epochRoot, DelegateHash: delegateRoot, VoteHash: voteRoot, @@ -326,19 +349,19 @@ func (d *DposContext) CommitTo(dbw trie.DatabaseWriter) (*DposContextProto, erro }, nil } -func (d *DposContext) CandidateTrie() *trie.Trie { return d.candidateTrie } -func (d *DposContext) DelegateTrie() *trie.Trie { return d.delegateTrie } -func (d *DposContext) VoteTrie() *trie.Trie { return d.voteTrie } -func (d *DposContext) EpochTrie() *trie.Trie { return d.epochTrie } -func (d *DposContext) MintCntTrie() *trie.Trie { return d.mintCntTrie } -func (d *DposContext) DB() ethdb.Database { return d.db } -func (dc *DposContext) SetEpoch(epoch *trie.Trie) { dc.epochTrie = epoch } -func (dc *DposContext) SetDelegate(delegate *trie.Trie) { dc.delegateTrie = delegate } -func (dc *DposContext) SetVote(vote *trie.Trie) { dc.voteTrie = vote } -func (dc *DposContext) SetCandidate(candidate *trie.Trie) { dc.candidateTrie = candidate } -func (dc *DposContext) SetMintCnt(mintCnt *trie.Trie) { dc.mintCntTrie = mintCnt } +func (d *LCPContext) CandidateTrie() *trie.Trie { return d.candidateTrie } +func (d *LCPContext) DelegateTrie() *trie.Trie { return d.delegateTrie } +func (d *LCPContext) VoteTrie() *trie.Trie { return d.voteTrie } +func (d *LCPContext) EpochTrie() *trie.Trie { return d.epochTrie } +func (d *LCPContext) MintCntTrie() *trie.Trie { return d.mintCntTrie } +func (d *LCPContext) DB() ethdb.Database { return d.db } +func (dc *LCPContext) SetEpoch(epoch *trie.Trie) { dc.epochTrie = epoch } +func (dc *LCPContext) SetDelegate(delegate *trie.Trie) { dc.delegateTrie = delegate } +func (dc *LCPContext) SetVote(vote *trie.Trie) { dc.voteTrie = vote } +func (dc *LCPContext) SetCandidate(candidate *trie.Trie) { dc.candidateTrie = candidate } +func (dc *LCPContext) SetMintCnt(mintCnt *trie.Trie) { dc.mintCntTrie = mintCnt } -func (dc *DposContext) GetValidators() ([]common.Address, error) { +func (dc *LCPContext) GetValidators() ([]common.Address, error) { var validators []common.Address key := []byte("validator") validatorsRLP := dc.epochTrie.Get(key) @@ -348,7 +371,7 @@ func (dc *DposContext) GetValidators() ([]common.Address, error) { return validators, nil } -func (dc *DposContext) SetValidators(validators []common.Address) error { +func (dc *LCPContext) SetValidators(validators []common.Address) error { key := []byte("validator") validatorsRLP, err := rlp.EncodeToBytes(validators) if err != nil { @@ -357,3 +380,15 @@ func (dc *DposContext) SetValidators(validators []common.Address) error { dc.epochTrie.Update(key, validatorsRLP) return nil } +func (dc *LCPContext) SetPeriod(period uint64) error { + dc.period = period + return nil +} +func (dc *LCPContext) SetMaxValidators(maxVal uint64) error { + dc.maxValidators = maxVal + return nil +} +func (dc *LCPContext) SetEpochInterval(interval uint64) error { + dc.epochInterval = interval + return nil +} diff --git a/core/types/dpos_context_test.go b/core/types/lcp_context_test.go similarity index 97% rename from core/types/dpos_context_test.go rename to core/types/lcp_context_test.go index 7de8fceb62..85384a1c00 100644 --- a/core/types/dpos_context_test.go +++ b/core/types/lcp_context_test.go @@ -3,9 +3,9 @@ package types import ( "testing" - "github.com/pavelkrolevets/go-ethereum/common" - "github.com/pavelkrolevets/go-ethereum/ethdb" - "github.com/pavelkrolevets/go-ethereum/trie" + "github.com/meitu/go-ethereum/common" + "github.com/meitu/go-ethereum/ethdb" + "github.com/meitu/go-ethereum/trie" "github.com/stretchr/testify/assert" ) diff --git a/eth/config.go b/eth/config.go index 5d5f7d59a4..6d726e62a5 100644 --- a/eth/config.go +++ b/eth/config.go @@ -114,6 +114,7 @@ type Config struct { // Miscellaneous options DocRoot string `toml:"-"` + LCP bool `toml:"-"` } type configMarshaling struct { diff --git a/params/config.go b/params/config.go index 934a847160..484e394501 100644 --- a/params/config.go +++ b/params/config.go @@ -30,55 +30,7 @@ var ( ) var ( - // MainnetChainConfig is the chain parameters to run a node on the main network. - MainnetChainConfig = &ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(1150000), - DAOForkBlock: big.NewInt(1920000), - DAOForkSupport: true, - EIP150Block: big.NewInt(2463000), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(2675000), - EIP158Block: big.NewInt(2675000), - ByzantiumBlock: big.NewInt(4370000), - ConstantinopleBlock: nil, - Ethash: new(EthashConfig), - } - - // TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network. - TestnetChainConfig = &ChainConfig{ - ChainID: big.NewInt(3), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), - EIP155Block: big.NewInt(10), - EIP158Block: big.NewInt(10), - ByzantiumBlock: big.NewInt(1700000), - ConstantinopleBlock: nil, - Ethash: new(EthashConfig), - } - - // RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network. - RinkebyChainConfig = &ChainConfig{ - ChainID: big.NewInt(4), - HomesteadBlock: big.NewInt(1), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(2), - EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"), - EIP155Block: big.NewInt(3), - EIP158Block: big.NewInt(3), - ByzantiumBlock: big.NewInt(1035301), - ConstantinopleBlock: nil, - Clique: &CliqueConfig{ - Period: 15, - Epoch: 30000, - }, - } - - DposChainConfig = &ChainConfig{ + LcpChainConfig= &ChainConfig{ ChainID: big.NewInt(1515), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -89,23 +41,9 @@ var ( EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(0), - Dpos: &DposConfig{}, + LCP: &LcpConfig{1, 30000, nil,nil}, } - // AllEthashProtocolChanges contains every protocol change (EIPs) introduced - // and accepted by the Ethereum core developers into the Ethash consensus. - // - // 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, nil} - - // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced - // and accepted by the Ethereum core developers into the Clique 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}, 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} + 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, &LcpConfig{1,3000,nil,nil}} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -135,7 +73,7 @@ type ChainConfig struct { // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` - Dpos *DposConfig `json:"dpos,omitempty"` + LCP *LcpConfig `json:"LCP,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -157,14 +95,17 @@ func (c *CliqueConfig) String() string { return "clique" } -// DposConfig is the consensus engine configs for delegated proof-of-stake based sealing. -type DposConfig struct { - Validators []common.Address `json:"validators"` // Genesis validator list +// LCP is the consensus engine. +type LcpConfig struct { + Period uint64 `json:"period"` // Number of seconds between blocks to enforce + Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint + MaxValidators uint64 `json:"MaxValidators"` + Validators []common.Address `json:"validators"` } // String implements the stringer interface, returning the consensus engine details. -func (d *DposConfig) String() string { - return "dpos" +func (c *LcpConfig) String() string { + return "lcp" } // String implements the fmt.Stringer interface. @@ -175,8 +116,8 @@ func (c *ChainConfig) String() string { engine = c.Ethash case c.Clique != nil: engine = c.Clique - case c.Dpos !=nil: - engine = c.Dpos + case c.LCP !=nil: + engine = c.LCP default: engine = "unknown" } diff --git a/trie/database.go b/trie/database.go index 0345c2788c..d1a70593ee 100644 --- a/trie/database.go +++ b/trie/database.go @@ -22,11 +22,11 @@ import ( "sync" "time" - "github.com/pavelkrolevets/go-ethereum/common" - "github.com/pavelkrolevets/go-ethereum/ethdb" - "github.com/pavelkrolevets/go-ethereum/log" - "github.com/pavelkrolevets/go-ethereum/metrics" - "github.com/pavelkrolevets/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" ) var ( @@ -51,27 +51,18 @@ const secureKeyLength = 11 + 32 // DatabaseReader wraps the Get and Has method of a backing store for the trie. type DatabaseReader interface { - // Get retrieves the value associated with key form the database. + // Get retrieves the value associated with key from the database. Get(key []byte) (value []byte, err error) // Has retrieves whether a key is present in the database. Has(key []byte) (bool, error) } -// DatabaseWriter wraps the Put method of a backing store for the trie. -type DatabaseWriter interface { - // Put stores the mapping key->value in the database. - // Implementations must not hold onto the value bytes, the trie - // will reuse the slice across calls to Put. - Put(key, value []byte) error -} - // Database is an intermediate write layer between the trie data structures and // the disk database. The aim is to accumulate trie writes in-memory and only // periodically flush a couple tries to disk, garbage collecting the remainder. type Database struct { diskdb ethdb.Database // Persistent storage for matured trie nodes - nodes map[common.Hash]*cachedNode // Data and references relationships of a node oldest common.Hash // Oldest tracked node, flush-list head newest common.Hash // Newest tracked node, flush-list tail @@ -439,6 +430,11 @@ func (db *Database) reference(child common.Hash, parent common.Hash) { // Dereference removes an existing reference from a root node. func (db *Database) Dereference(root common.Hash) { + // Sanity check to ensure that the meta-root is not removed + if root == (common.Hash{}) { + log.Error("Attempted to dereference the trie cache meta root") + return + } db.lock.Lock() defer db.lock.Unlock() diff --git a/trie/trie.go b/trie/trie.go index 16854bc7ac..0e9ec07013 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -71,7 +71,7 @@ type LeafCallback func(leaf []byte, parent common.Hash) error // // Trie is not safe for concurrent use. type Trie struct { - db Database + db *Database root node originalRoot common.Hash prefix []byte @@ -99,7 +99,7 @@ func (t *Trie) newFlag() nodeFlag { // trie is initially empty and does not require a database. Otherwise, // New will panic if db is nil and returns a MissingNodeError if root does // not exist in the database. Accessing the trie loads nodes from db on demand. -func New(root common.Hash, db Database) (*Trie, error) { +func New(root common.Hash, db *Database) (*Trie, error) { trie := &Trie{ db: db, @@ -119,8 +119,9 @@ func New(root common.Hash, db Database) (*Trie, error) { } return trie, nil } + // Creates trie with prefix for dpos content -func NewTrieWithPrefix(root common.Hash, prefix []byte, db Database) (*Trie, error) { +func NewTrieWithPrefix(root common.Hash, prefix []byte, db *Database) (*Trie, error) { trie, err := New(root, db) if err != nil { return nil, err @@ -129,7 +130,6 @@ func NewTrieWithPrefix(root common.Hash, prefix []byte, db Database) (*Trie, err return trie, nil } - // PrefixIterator returns an iterator that returns nodes of the trie which has the prefix path specificed // Iteration starts at the key after the given start key. func (t *Trie) PrefixIterator(prefix []byte) NodeIterator {