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 {