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 {