From c9e3e998a84c3717b3d703ba3941bf00e1a7d746 Mon Sep 17 00:00:00 2001 From: pavelkrolevets Date: Mon, 20 Aug 2018 14:41:08 +0800 Subject: [PATCH] ADDED LCP engine --- consensus/clique/api.go | 119 ---- consensus/clique/clique.go | 689 ------------------- consensus/clique/snapshot.go | 312 --------- consensus/clique/snapshot_test.go | 406 ------------ consensus/lcp/api.go | 85 +-- consensus/lcp/epoch_context_test.go | 359 ++++++++++ consensus/lcp/epoch_cotext.go | 458 ++++++------- consensus/lcp/lcp.go | 980 ++++++++++++---------------- core/genesis.go | 2 +- core/types/block.go | 13 +- core/types/lcp_context_test.go | 6 +- params/config.go | 35 +- trie/database.go | 22 +- trie/trie.go | 2 +- 14 files changed, 1013 insertions(+), 2475 deletions(-) delete mode 100644 consensus/clique/api.go delete mode 100644 consensus/clique/clique.go delete mode 100644 consensus/clique/snapshot.go delete mode 100644 consensus/clique/snapshot_test.go create mode 100644 consensus/lcp/epoch_context_test.go diff --git a/consensus/clique/api.go b/consensus/clique/api.go deleted file mode 100644 index 532e33582d..0000000000 --- a/consensus/clique/api.go +++ /dev/null @@ -1,119 +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 clique - -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 - clique *Clique -} - -// 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.clique.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) -} diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go deleted file mode 100644 index 30b9fd5726..0000000000 --- a/consensus/clique/clique.go +++ /dev/null @@ -1,689 +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 clique implements the proof-of-authority consensus engine. -package clique - -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 -) - -// Clique proof-of-authority 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 signer - nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer. - - uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. - - diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures - diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures -) - -// Various error messages to mark blocks invalid. These should be private to -// prevent engine specific errors from being referenced in the remainder of the -// codebase, inherently breaking if the engine is swapped out. Please put common -// error types into the consensus package. -var ( - // errUnknownBlock is returned when the list of signers is requested for a block - // that is not part of the local blockchain. - errUnknownBlock = errors.New("unknown block") - - // errInvalidCheckpointBeneficiary is returned if a checkpoint/epoch transition - // block has a beneficiary set to non-zeroes. - errInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero") - - // errInvalidVote is returned if a nonce value is something else that the two - // allowed constants of 0x00..0 or 0xff..f. - errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f") - - // errInvalidCheckpointVote is returned if a checkpoint/epoch transition block - // has a vote nonce set to non-zeroes. - errInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero") - - // errMissingVanity is returned if a block's extra-data section is shorter than - // 32 bytes, which is required to store the signer vanity. - errMissingVanity = errors.New("extra-data 32 byte vanity prefix missing") - - // errMissingSignature is returned if a block's extra-data section doesn't seem - // to contain a 65 byte secp256k1 signature. - errMissingSignature = errors.New("extra-data 65 byte suffix signature missing") - - // errExtraSigners is returned if non-checkpoint block contain signer data in - // their extra-data fields. - errExtraSigners = errors.New("non-checkpoint block contains extra signer list") - - // errInvalidCheckpointSigners is returned if a checkpoint block contains an - // invalid list of signers (i.e. non divisible by 20 bytes, or not the correct - // ones). - errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block") - - // 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 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.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short - header.MixDigest, - header.Nonce, - }) - hasher.Sum(hash[:0]) - return hash -} - -// 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 Clique struct { - config *params.CliqueConfig // 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 -} - -// New creates a Clique proof-of-authority consensus engine with the initial -// signers set to the ones provided by the user. -func New(config *params.CliqueConfig, 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 &Clique{ - 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 *Clique) Author(header *types.Header) (common.Address, error) { - return ecrecover(header, c.signatures) -} - -// VerifyHeader checks whether a header conforms to the consensus rules. -func (c *Clique) 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/consensus/clique/snapshot.go b/consensus/clique/snapshot.go deleted file mode 100644 index 4aaac99b75..0000000000 --- a/consensus/clique/snapshot.go +++ /dev/null @@ -1,312 +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 clique - -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/clique/snapshot_test.go b/consensus/clique/snapshot_test.go deleted file mode 100644 index 038997623e..0000000000 --- a/consensus/clique/snapshot_test.go +++ /dev/null @@ -1,406 +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 clique - -import ( - "bytes" - "crypto/ecdsa" - "math/big" - "testing" - - "github.com/pavelkrolevets/go-ethereum/common" - "github.com/pavelkrolevets/go-ethereum/core" - "github.com/pavelkrolevets/go-ethereum/core/rawdb" - "github.com/pavelkrolevets/go-ethereum/core/types" - "github.com/pavelkrolevets/go-ethereum/crypto" - "github.com/pavelkrolevets/go-ethereum/ethdb" - "github.com/pavelkrolevets/go-ethereum/params" -) - -type testerVote struct { - signer string - voted string - auth bool -} - -// testerAccountPool is a pool to maintain currently active tester accounts, -// mapped from textual names used in the tests below to actual Ethereum private -// keys capable of signing transactions. -type testerAccountPool struct { - accounts map[string]*ecdsa.PrivateKey -} - -func newTesterAccountPool() *testerAccountPool { - return &testerAccountPool{ - accounts: make(map[string]*ecdsa.PrivateKey), - } -} - -func (ap *testerAccountPool) sign(header *types.Header, signer string) { - // Ensure we have a persistent key for the signer - if ap.accounts[signer] == nil { - ap.accounts[signer], _ = crypto.GenerateKey() - } - // Sign the header and embed the signature in extra data - sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer]) - copy(header.Extra[len(header.Extra)-65:], sig) -} - -func (ap *testerAccountPool) address(account string) common.Address { - // Ensure we have a persistent key for the account - if ap.accounts[account] == nil { - ap.accounts[account], _ = crypto.GenerateKey() - } - // Resolve and return the Ethereum address - return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) -} - -// testerChainReader implements consensus.ChainReader to access the genesis -// block. All other methods and requests will panic. -type testerChainReader struct { - db ethdb.Database -} - -func (r *testerChainReader) Config() *params.ChainConfig { return params.AllCliqueProtocolChanges } -func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") } -func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") } -func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") } -func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header { panic("not supported") } -func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header { - if number == 0 { - return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0) - } - panic("not supported") -} - -// Tests that voting is evaluated correctly for various simple and complex scenarios. -func TestVoting(t *testing.T) { - // Define the various voting scenarios to test - tests := []struct { - epoch uint64 - signers []string - votes []testerVote - results []string - }{ - { - // Single signer, no votes cast - signers: []string{"A"}, - votes: []testerVote{{signer: "A"}}, - results: []string{"A"}, - }, { - // Single signer, voting to add two others (only accept first, second needs 2 votes) - signers: []string{"A"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Two signers, voting to add three others (only accept first two, third needs 3 votes already) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B", voted: "C", auth: true}, - {signer: "A", voted: "D", auth: true}, - {signer: "B", voted: "D", auth: true}, - {signer: "C"}, - {signer: "A", voted: "E", auth: true}, - {signer: "B", voted: "E", auth: true}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Single signer, dropping itself (weird, but one less cornercase by explicitly allowing this) - signers: []string{"A"}, - votes: []testerVote{ - {signer: "A", voted: "A", auth: false}, - }, - results: []string{}, - }, { - // Two signers, actually needing mutual consent to drop either of them (not fulfilled) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Two signers, actually needing mutual consent to drop either of them (fulfilled) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - {signer: "B", voted: "B", auth: false}, - }, - results: []string{"A"}, - }, { - // Three signers, two of them deciding to drop the third - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Four signers, consensus of two not being enough to drop anyone - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Four signers, consensus of three already being enough to drop someone - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - }, - results: []string{"A", "B", "C"}, - }, { - // Authorizations are counted once per signer per target - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Authorizing multiple accounts concurrently is permitted - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "D", auth: true}, - {signer: "B"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: true}, - {signer: "A"}, - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Deauthorizations are counted once per signer per target - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - {signer: "B"}, - {signer: "A", voted: "B", auth: false}, - {signer: "B"}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Deauthorizing multiple accounts concurrently is permitted - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Votes from deauthorized signers are discarded immediately (deauth votes) - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "C", voted: "B", auth: false}, - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Votes from deauthorized signers are discarded immediately (auth votes) - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "C", voted: "B", auth: false}, - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Cascading changes are not allowed, only the account being voted on may change - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - }, - results: []string{"A", "B", "C"}, - }, { - // Changes reaching consensus out of bounds (via a deauth) execute on touch - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "C", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B", "C"}, - }, { - // Ensure that pending votes don't survive authorization status changes. This - // corner case can only appear if a signer is quickly added, removed and then - // readded (or the inverse), while one of the original voters dropped. If a - // past vote is left cached in the system somewhere, this will interfere with - // the final signer outcome. - signers: []string{"A", "B", "C", "D", "E"}, - votes: []testerVote{ - {signer: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed - {signer: "B", voted: "F", auth: true}, - {signer: "C", voted: "F", auth: true}, - {signer: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") - {signer: "E", voted: "F", auth: false}, - {signer: "B", voted: "F", auth: false}, - {signer: "C", voted: "F", auth: false}, - {signer: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed - {signer: "E", voted: "F", auth: true}, - {signer: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed - {signer: "C", voted: "A", auth: false}, - {signer: "D", voted: "A", auth: false}, - {signer: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed - }, - results: []string{"B", "C", "D", "E", "F"}, - }, { - // Epoch transitions reset all votes to allow chain checkpointing - epoch: 3, - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots) - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, - } - // Run through the scenarios and test them - for i, tt := range tests { - // Create the account pool and generate the initial set of signers - accounts := newTesterAccountPool() - - signers := make([]common.Address, len(tt.signers)) - for j, signer := range tt.signers { - signers[j] = accounts.address(signer) - } - for j := 0; j < len(signers); j++ { - for k := j + 1; k < len(signers); k++ { - if bytes.Compare(signers[j][:], signers[k][:]) > 0 { - signers[j], signers[k] = signers[k], signers[j] - } - } - } - // Create the genesis block with the initial set of signers - genesis := &core.Genesis{ - ExtraData: make([]byte, extraVanity+common.AddressLength*len(signers)+extraSeal), - } - for j, signer := range signers { - copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:]) - } - // Create a pristine blockchain with the genesis injected - db := ethdb.NewMemDatabase() - genesis.Commit(db) - - // Assemble a chain of headers from the cast votes - headers := make([]*types.Header, len(tt.votes)) - for j, vote := range tt.votes { - headers[j] = &types.Header{ - Number: big.NewInt(int64(j) + 1), - Time: big.NewInt(int64(j) * 15), - Coinbase: accounts.address(vote.voted), - Extra: make([]byte, extraVanity+extraSeal), - } - if j > 0 { - headers[j].ParentHash = headers[j-1].Hash() - } - if vote.auth { - copy(headers[j].Nonce[:], nonceAuthVote) - } - accounts.sign(headers[j], vote.signer) - } - // Pass all the headers through clique and ensure tallying succeeds - head := headers[len(headers)-1] - - snap, err := New(¶ms.CliqueConfig{Epoch: tt.epoch}, db).snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers) - if err != nil { - t.Errorf("test %d: failed to create voting snapshot: %v", i, err) - continue - } - // Verify the final list of signers against the expected ones - signers = make([]common.Address, len(tt.results)) - for j, signer := range tt.results { - signers[j] = accounts.address(signer) - } - for j := 0; j < len(signers); j++ { - for k := j + 1; k < len(signers); k++ { - if bytes.Compare(signers[j][:], signers[k][:]) > 0 { - signers[j], signers[k] = signers[k], signers[j] - } - } - } - result := snap.signers() - if len(result) != len(signers) { - t.Errorf("test %d: signers mismatch: have %x, want %x", i, result, signers) - continue - } - for j := 0; j < len(result); j++ { - if !bytes.Equal(result[j][:], signers[j][:]) { - t.Errorf("test %d, signer %d: signer mismatch: have %x, want %x", i, j, result[j], signers[j]) - } - } - } -} diff --git a/consensus/lcp/api.go b/consensus/lcp/api.go index 37110bfe55..8a895d3dcc 100644 --- a/consensus/lcp/api.go +++ b/consensus/lcp/api.go @@ -22,6 +22,7 @@ import ( "github.com/pavelkrolevets/go-ethereum/consensus" "github.com/pavelkrolevets/go-ethereum/core/types" "github.com/pavelkrolevets/go-ethereum/rpc" + "math/big" ) // API is a user facing RPC API to allow controlling the signer and voting @@ -31,90 +32,40 @@ type API struct { 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) +// 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())) } - // 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) + epochTrie, err := types.NewEpochTrie(header.LCPContext.EpochHash, api.lcp.db) 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) + LCPContext := types.LCPContext{} + LCPContext.SetEpoch(epochTrie) + validators, err := LCPContext.GetValidators() if err != nil { return nil, err } - return snap.signers(), nil + return validators, 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 +// GetConfirmedBlockNumber retrieves the latest irreversible block +func (api *API) GetConfirmedBlockNumber() (*big.Int, error) { + var err error + header := api.lcp.confirmedBlockHeader + if header == nil { + header, err = api.lcp.loadConfirmedBlockHeader(api.chain) + if err != nil { + return nil, err + } } - return proposals + return header.Number, nil } - -// 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_context_test.go b/consensus/lcp/epoch_context_test.go new file mode 100644 index 0000000000..97cf77db26 --- /dev/null +++ b/consensus/lcp/epoch_context_test.go @@ -0,0 +1,359 @@ +package lcp + +import ( + "math/big" + "testing" + + "github.com/pavelkrolevets/go-ethereum/common" + "github.com/pavelkrolevets/go-ethereum/core/state" + "github.com/pavelkrolevets/go-ethereum/core/types" + "github.com/pavelkrolevets/go-ethereum/ethdb" + "github.com/pavelkrolevets/go-ethereum/trie" + + "github.com/stretchr/testify/assert" + //types2 "github.com/pavelkrolevets/go-ethereum/core/types" +) + +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() + bdb := trie.NewDatabase(db) + stateDB, _ := state.New(common.Hash{}, state.NewDatabase(db)) + LCPContext, err := types.NewLCPContext(bdb) + assert.Nil(t, err) + + epochContext := &EpochContext{ + Context: LCPContext, + statedb: stateDB, + } + _, err = epochContext.countVotes() + assert.NotNil(t, err) + + for candidate, electors := range voteMap { + assert.Nil(t, LCPContext.BecomeCandidate(candidate)) + for _, elector := range electors { + stateDB.SetBalance(elector, big.NewInt(balance)) + assert.Nil(t, LCPContext.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/epoch_cotext.go b/consensus/lcp/epoch_cotext.go index b737ee2ca8..272d0f39a3 100644 --- a/consensus/lcp/epoch_cotext.go +++ b/consensus/lcp/epoch_cotext.go @@ -1,314 +1,226 @@ - -// 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" + "encoding/binary" + "errors" + "fmt" + "math/big" + "math/rand" + "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" + "github.com/pavelkrolevets/go-ethereum/common" + "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/log" + "github.com/pavelkrolevets/go-ethereum/trie" ) -// 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 +type EpochContext struct { + TimeStamp int64 + Context *types.LCPContext + statedb *state.StateDB } -// 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 -} +// countVotes +func (ec *EpochContext) countVotes() (votes map[common.Address]*big.Int, err error) { + votes = map[common.Address]*big.Int{} + delegateTrie := ec.Context.DelegateTrie() + candidateTrie := ec.Context.CandidateTrie() + statedb := ec.statedb -// 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), + iterCandidate := trie.NewIterator(candidateTrie.NodeIterator(nil)) + existCandidate := iterCandidate.Next() + if !existCandidate { + return votes, errors.New("no candidates") } - for _, signer := range signers { - snap.Signers[signer] = struct{}{} + 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 snap + return votes, nil } -// 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[:]...)) +func (ec *EpochContext) kickoutValidator(epoch int64) error { + validators, err := ec.Context.GetValidators() + var epochDuration int64 + var blockInterval int64 + var maxValidatorSize int64 if err != nil { - return nil, err + return fmt.Errorf("failed to get validator: %s", err) } - snap := new(Snapshot) - if err := json.Unmarshal(blob, snap); err != nil { - return nil, err + if len(validators) == 0 { + return errors.New("no validator could be kickout") } - snap.config = config - snap.sigcache = sigcache - return snap, nil + epochDuration = ec.Context.GetEpochInterval() + // 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 < epochDuration { + epochDuration = ec.TimeStamp - timeOfFirstBlock + } + blockInterval = ec.Context.GetPeriodBlock() + maxValidatorSize = ec.Context.GetMaxValidators() + 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.Context.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.Context.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.Context.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 } -// store inserts the snapshot into the database. -func (s *Snapshot) store(db ethdb.Database) error { - blob, err := json.Marshal(s) +func (ec *EpochContext) lookupValidator(now int64) (validator common.Address, err error) { + validator = common.Address{} + offset := now % ec.Context.GetEpochInterval() + if offset%ec.Context.GetPeriodBlock() != 0 { + return common.Address{}, ErrInvalidMintBlockTime + } + offset /= ec.Context.GetPeriodBlock() + + validators, err := ec.Context.GetValidators() if err != nil { - return err + return common.Address{}, err } - return db.Put(append([]byte("clique-"), s.Hash[:]...), blob) + validatorSize := len(validators) + if validatorSize == 0 { + return common.Address{}, errors.New("failed to lookup validator") + } + offset %= int64(validatorSize) + return validators[offset], nil } +// Changed LCP context constants to vars from the tries +func (ec *EpochContext) tryElect(genesis, parent *types.Header) error { + genesisEpoch := genesis.Time.Int64() / ec.Context.GetEpochInterval() + prevEpoch := parent.Time.Int64() / ec.Context.GetEpochInterval() + currentEpoch := ec.TimeStamp / ec.Context.GetEpochInterval() + safeSize:= int(ec.Context.GetMaxValidators()*2/3 + 1) -// 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), + prevEpochIsGenesis := prevEpoch == genesisEpoch + if prevEpochIsGenesis && prevEpoch < currentEpoch { + prevEpoch = currentEpoch - 1 } - 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 + prevEpochBytes := make([]byte, 8) + binary.BigEndian.PutUint64(prevEpochBytes, uint64(prevEpoch)) + iter := trie.NewIterator(ec.Context.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 + } } - } - 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) + votes, err := ec.countVotes() if err != nil { - return nil, err + return err } - if _, ok := snap.Signers[signer]; !ok { - return nil, errUnauthorized + candidates := sortableAddresses{} + for candidate, cnt := range votes { + candidates = append(candidates, &sortableAddress{candidate, cnt}) } - for _, recent := range snap.Recents { - if recent == signer { - return nil, errUnauthorized - } + if len(candidates) < safeSize { + return errors.New("too few candidates") + } + sort.Sort(candidates) + if len(candidates) > int(ec.Context.GetMaxValidators()) { + candidates = candidates[:ec.Context.GetMaxValidators()] } - 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 - } + // 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] } - // 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 + sortedValidators := make([]common.Address, 0) + for _, candidate := range candidates { + sortedValidators = append(sortedValidators, candidate.address) } - 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) - } + epochTrie, _ := types.NewEpochTrie(common.Hash{}, ec.Context.DB()) + ec.Context.SetEpoch(epochTrie) + ec.Context.SetValidators(sortedValidators) + log.Info("Come to new epoch", "prevEpoch", i, "nextEpoch", i+1) } - snap.Number += uint64(len(headers)) - snap.Hash = headers[len(headers)-1].Hash() - - return snap, nil + return 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 +type sortableAddress struct { + address common.Address + weight *big.Int } +type sortableAddresses []*sortableAddress -// 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++ +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() } - return (number % uint64(len(signers))) == uint64(offset) } - diff --git a/consensus/lcp/lcp.go b/consensus/lcp/lcp.go index 74364a4079..c7c7688a15 100644 --- a/consensus/lcp/lcp.go +++ b/consensus/lcp/lcp.go @@ -5,124 +5,99 @@ 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" + "github.com/hashicorp/golang-lru" + "encoding/binary" + "fmt" + "github.com/pavelkrolevets/go-ethereum/trie" ) 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 + 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 - 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 + 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") + blockInterval = int64(params.LcpChainConfig.LCP.Period*2/3+1) + consensusSize = int(params.LcpChainConfig.LCP.MaxValidators*2/3+1) + safeSize = int(params.LcpChainConfig.LCP.MaxValidators*2/3+1) + epochInterval = int64(params.LcpChainConfig.LCP.EpochInterval) ) -// 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. + 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") - - // 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") + 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. ) -// SignerFn is a signer callback function to request a hash to be signed by a -// backing account. +type LCP struct { + config *params.LcpConfig // Consensus engine configuration parameters + db *trie.Database // Database to store and retrieve snapshots + + 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) -// sigHash returns the hash which is used as input for the LCP +// 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. // @@ -135,6 +110,7 @@ func sigHash(header *types.Header) (hash common.Hash) { rlp.Encode(hasher, []interface{}{ header.ParentHash, header.UncleHash, + header.Validator, header.Coinbase, header.Root, header.TxHash, @@ -149,12 +125,352 @@ func sigHash(header *types.Header) (hash common.Hash) { header.MixDigest, header.Nonce, header.LCPContext.Root(), - }) hasher.Sum(hash[:0]) return hash } +func New(config *params.LcpConfig, db *trie.Database) *LCP { + signatures, _ := lru.NewARC(inmemorySignatures) + return &LCP{ + config: config, + db: db, + signatures: signatures, + } +} + +func (d *LCP) Author(header *types.Header) (common.Address, error) { + return header.Validator, nil +} + +func (d *LCP) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { + return d.verifyHeader(chain, header, nil) +} + +func (d *LCP) 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(d.config.Period) > header.Time.Uint64() { + return ErrInvalidTimestamp + } + return nil +} + +func (d *LCP) 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 *LCP) 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 *LCP) VerifySeal(chain consensus.ChainReader, header *types.Header) error { + return d.verifySeal(chain, header, nil) +} + +func (d *LCP) 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.NewLCPContextFromProto(d.db, parent.LCPContext) + if err != nil { + return err + } + epochContext := &EpochContext{Context: 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 *LCP) 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 *LCP) 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() / d.config.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 *LCP) loadConfirmedBlockHeader(chain consensus.ChainReader) (*types.Header, error) { + db := s.db.DiskDB() + key, err:=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 *LCP) storeConfirmedBlockHeader(db *trie.Database) error { + db_wr := s.db.DiskDBwrite() + return db_wr.Put(confirmedBlockHead, s.confirmedBlockHeader.Hash().Bytes()) +} + +func (d *LCP) 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 *LCP) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, + uncles []*types.Header, receipts []*types.Receipt, LCPContext *types.LCPContext) (*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, + Context: LCPContext, + 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, LCPContext) + header.LCPContext = LCPContext.ToProto() + return types.NewBlock(header, txs, uncles, receipts), nil +} + +func (d *LCP) 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 *LCP) CheckValidator(lastBlock *types.Block, now int64) error { + if err := d.checkDeadline(lastBlock, now); err != nil { + return err + } + dposContext, err := types.NewLCPContextFromProto(d.db, lastBlock.Header().LCPContext) + if err != nil { + return err + } + epochContext := &EpochContext{Context: 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 *LCP) 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 *LCP) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { + return big.NewInt(1) +} + +func (d *LCP) APIs(chain consensus.ChainReader) []rpc.API { + return []rpc.API{{ + Namespace: "dpos", + Version: "1.0", + Service: &API{chain: chain, lcp: d}, + Public: true, + }} +} + +func (d *LCP) 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 @@ -167,7 +483,6 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, er 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 { @@ -175,506 +490,45 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, er } 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 +func PrevSlot(now int64) int64 { + return int64((now-1)/blockInterval) * blockInterval } -// 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), - } +func NextSlot(now int64) int64 { + return int64((now+blockInterval-1)/blockInterval) * blockInterval } -// 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) -} +// update counts in MintCntTrie for the miner of newBlock +func updateMintCnt(parentBlockTime, currentBlockTime int64, validator common.Address, dposContext *types.LCPContext) { + currentMintCntTrie := dposContext.MintCntTrie() + currentEpoch := parentBlockTime / epochInterval + currentEpochBytes := make([]byte, 8) + binary.BigEndian.PutUint64(currentEpochBytes, uint64(currentEpoch)) -// 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) -} + cnt := int64(1) + newEpoch := currentBlockTime / epochInterval + // still during the currentEpochID + if currentEpoch == newEpoch { + iter := trie.NewIterator(currentMintCntTrie.NodeIterator(currentEpochBytes)) -// 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)) + // when current is not genesis, read last count from the MintCntTrie + if iter.Next() { + cntBytes := currentMintCntTrie.Get(append(currentEpochBytes, validator.Bytes()...)) - 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 + // not the first time to mint + if cntBytes != nil { + cnt = int64(binary.BigEndian.Uint64(cntBytes)) + 1 } } } - // 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 + + 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) } - -// 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/genesis.go b/core/genesis.go index 8afddde14c..dcf0e7eccc 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -341,7 +341,7 @@ func initGenesisLCPContext(g *Genesis, db ethdb.Database) *types.LCPContext { } } if g.Config != nil && g.Config.LCP != nil && g.Config.LCP.Period != 0 { - dc.SetPeriod(g.Config.LCP.Period) + dc.SetPeriodBlock(g.Config.LCP.Period) } if g.Config != nil && g.Config.LCP != nil && g.Config.LCP.MaxValidators != 0 { dc.SetMaxValidators(g.Config.LCP.MaxValidators) diff --git a/core/types/block.go b/core/types/block.go index be36619c32..1b92587383 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -259,9 +259,9 @@ func CopyHeader(h *Header) *Header { copy(cpy.Extra, h.Extra) } // add dposContextProto to header - cpy.DposContext = &DposContextProto{} - if h.DposContext != nil { - cpy.DposContext = h.DposContext + cpy.LCPContext = &LCPContextProto{} + if h.LCPContext != nil { + cpy.LCPContext = h.LCPContext } return &cpy } @@ -372,7 +372,7 @@ func (b *Block) WithSeal(header *Header) *Block { transactions: b.transactions, uncles: b.uncles, // add dposcontext - DposContext: b.DposContext, + LCPContext: b.LCPContext, } } @@ -425,7 +425,7 @@ func (h *Header) String() string { Root: %x TxSha %x ReceiptSha: %x - DposContext: %x + LCPContext: %x Bloom: %x Difficulty: %v Number: %v @@ -435,7 +435,8 @@ func (h *Header) String() string { Extra: %s MixDigest: %x Nonce: %x -]`, h.Hash(), h.ParentHash, h.UncleHash, h.Validator, h.Coinbase, h.Root, h.TxHash, h.ReceiptHash, h.DposContext, h.Bloom, h.Difficulty, h.Number, h.GasLimit, h.GasUsed, h.Time, h.Extra, h.MixDigest, h.Nonce) + +]`, h.Hash(), h.ParentHash, h.UncleHash, h.Validator, h.Coinbase, h.Root, h.TxHash, h.ReceiptHash, h.LCPContext, h.Bloom, h.Difficulty, h.Number, h.GasLimit, h.GasUsed, h.Time, h.Extra, h.MixDigest, h.Nonce) } type Blocks []*Block diff --git a/core/types/lcp_context_test.go b/core/types/lcp_context_test.go index 85384a1c00..7de8fceb62 100644 --- a/core/types/lcp_context_test.go +++ b/core/types/lcp_context_test.go @@ -3,9 +3,9 @@ package types import ( "testing" - "github.com/meitu/go-ethereum/common" - "github.com/meitu/go-ethereum/ethdb" - "github.com/meitu/go-ethereum/trie" + "github.com/pavelkrolevets/go-ethereum/common" + "github.com/pavelkrolevets/go-ethereum/ethdb" + "github.com/pavelkrolevets/go-ethereum/trie" "github.com/stretchr/testify/assert" ) diff --git a/params/config.go b/params/config.go index 484e394501..56ff8131c3 100644 --- a/params/config.go +++ b/params/config.go @@ -41,9 +41,9 @@ var ( EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(0), - LCP: &LcpConfig{1, 30000, nil,nil}, + LCP: &LcpConfig{1, 30000, 3,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}} + 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, &LcpConfig{1,3000,3,nil}} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -71,35 +71,14 @@ type ChainConfig struct { ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"` // Constantinople switch block (nil = no fork, 0 = already activated) // Various consensus engines - Ethash *EthashConfig `json:"ethash,omitempty"` - Clique *CliqueConfig `json:"clique,omitempty"` LCP *LcpConfig `json:"LCP,omitempty"` } -// EthashConfig is the consensus engine configs for proof-of-work based sealing. -type EthashConfig struct{} - -// String implements the stringer interface, returning the consensus engine details. -func (c *EthashConfig) String() string { - return "ethash" -} - -// CliqueConfig is the consensus engine configs for proof-of-authority based sealing. -type CliqueConfig struct { - Period uint64 `json:"period"` // Number of seconds between blocks to enforce - Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint -} - -// String implements the stringer interface, returning the consensus engine details. -func (c *CliqueConfig) String() string { - return "clique" -} - // 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"` + Period int64 `json:"period"` // Number of seconds between blocks to enforce + EpochInterval int64 `json:"epoch"` // Epoch length to reset votes and checkpoint + MaxValidators int64 `json:"MaxValidators"` Validators []common.Address `json:"validators"` } @@ -112,10 +91,6 @@ func (c *LcpConfig) String() string { func (c *ChainConfig) String() string { var engine interface{} switch { - case c.Ethash != nil: - engine = c.Ethash - case c.Clique != nil: - engine = c.Clique case c.LCP !=nil: engine = c.LCP default: diff --git a/trie/database.go b/trie/database.go index d1a70593ee..bd30223730 100644 --- a/trie/database.go +++ b/trie/database.go @@ -22,11 +22,11 @@ import ( "sync" "time" - "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" + "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" ) var ( @@ -57,12 +57,20 @@ type DatabaseReader interface { // 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 @@ -274,6 +282,10 @@ func NewDatabase(diskdb ethdb.Database) *Database { func (db *Database) DiskDB() DatabaseReader { return db.diskdb } +// DiskDBwrite writes the persistent storage backing the trie database. +func (db *Database) DiskDBwrite() DatabaseWriter { + return db.diskdb +} // InsertBlob writes a new reference tracked blob to the memory database if it's // yet unknown. This method should only be used for non-trie nodes that require diff --git a/trie/trie.go b/trie/trie.go index 0e9ec07013..d060d0b4bc 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -120,7 +120,7 @@ func New(root common.Hash, db *Database) (*Trie, error) { return trie, nil } -// Creates trie with prefix for dpos content +// Creates trie with prefix for LCP content func NewTrieWithPrefix(root common.Hash, prefix []byte, db *Database) (*Trie, error) { trie, err := New(root, db) if err != nil {