123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- package qbftengine
- import (
- "bytes"
- "fmt"
- "math/big"
- "time"
- "github.com/ethereum/go-ethereum/accounts/abi/bind"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/consensus/istanbul"
- "github.com/ethereum/go-ethereum/consensus/istanbul/backend/contract"
- istanbulcommon "github.com/ethereum/go-ethereum/consensus/istanbul/common"
- "github.com/ethereum/go-ethereum/consensus/istanbul/validator"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "golang.org/x/crypto/sha3"
- )
- var (
- nilUncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
- )
- type SignerFn func(data []byte) ([]byte, error)
- type Engine struct {
- cfg *istanbul.Config
- signer common.Address // Ethereum address of the signing key
- sign SignerFn // Signer function to authorize hashes with
- }
- func NewEngine(cfg *istanbul.Config, signer common.Address, sign SignerFn) *Engine {
- return &Engine{
- cfg: cfg,
- signer: signer,
- sign: sign,
- }
- }
- func (e *Engine) Author(header *types.Header) (common.Address, error) {
- return header.Coinbase, nil
- }
- func (e *Engine) CommitHeader(header *types.Header, seals [][]byte, round *big.Int) error {
- return ApplyHeaderQBFTExtra(
- header,
- writeCommittedSeals(seals),
- writeRoundNumber(round),
- )
- }
- // writeCommittedSeals writes the extra-data field of a block header with given committed seals.
- func writeCommittedSeals(committedSeals [][]byte) ApplyQBFTExtra {
- return func(qbftExtra *types.QBFTExtra) error {
- if len(committedSeals) == 0 {
- return istanbulcommon.ErrInvalidCommittedSeals
- }
- for _, seal := range committedSeals {
- if len(seal) != types.IstanbulExtraSeal {
- return istanbulcommon.ErrInvalidCommittedSeals
- }
- }
- qbftExtra.CommittedSeal = make([][]byte, len(committedSeals))
- copy(qbftExtra.CommittedSeal, committedSeals)
- return nil
- }
- }
- // writeRoundNumber writes the extra-data field of a block header with given round.
- func writeRoundNumber(round *big.Int) ApplyQBFTExtra {
- return func(qbftExtra *types.QBFTExtra) error {
- qbftExtra.Round = uint32(round.Uint64())
- return nil
- }
- }
- func (e *Engine) VerifyBlockProposal(chain consensus.ChainHeaderReader, block *types.Block, validators istanbul.ValidatorSet) (time.Duration, error) {
- // check block body
- txnHash := types.DeriveSha(block.Transactions(), new(trie.Trie))
- if txnHash != block.Header().TxHash {
- return 0, istanbulcommon.ErrMismatchTxhashes
- }
- uncleHash := types.CalcUncleHash(block.Uncles())
- if uncleHash != nilUncleHash {
- return 0, istanbulcommon.ErrInvalidUncleHash
- }
- // verify the header of proposed block
- err := e.VerifyHeader(chain, block.Header(), nil, validators)
- if err == nil || err == istanbulcommon.ErrEmptyCommittedSeals {
- // ignore errEmptyCommittedSeals error because we don't have the committed seals yet
- return 0, nil
- } else if err == consensus.ErrFutureBlock {
- return time.Until(time.Unix(int64(block.Header().Time), 0)), consensus.ErrFutureBlock
- }
- parentHeader := chain.GetHeaderByHash(block.ParentHash())
- config := e.cfg.GetConfig(parentHeader.Number)
- if config.EmptyBlockPeriod > config.BlockPeriod && len(block.Transactions()) == 0 {
- if block.Header().Time < parentHeader.Time+config.EmptyBlockPeriod {
- return 0, fmt.Errorf("empty block verification fail")
- }
- }
- return 0, err
- }
- func (e *Engine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, validators istanbul.ValidatorSet) error {
- return e.verifyHeader(chain, header, parents, validators)
- }
- // 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 (e *Engine) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, validators istanbul.ValidatorSet) error {
- if header.Number == nil {
- return istanbulcommon.ErrUnknownBlock
- }
- // Don't waste time checking blocks from the future (adjusting for allowed threshold)
- adjustedTimeNow := time.Now().Add(time.Duration(e.cfg.AllowedFutureBlockTime) * time.Second).Unix()
- if header.Time > uint64(adjustedTimeNow) {
- return consensus.ErrFutureBlock
- }
- if _, err := types.ExtractQBFTExtra(header); err != nil {
- return istanbulcommon.ErrInvalidExtraDataFormat
- }
- // Ensure that the mix digest is zero as we don't have fork protection currently
- if header.MixDigest != types.IstanbulDigest {
- return istanbulcommon.ErrInvalidMixDigest
- }
- // Ensure that the block doesn't contain any uncles which are meaningless in Istanbul
- if header.UncleHash != nilUncleHash {
- return istanbulcommon.ErrInvalidUncleHash
- }
- // Ensure that the block's difficulty is meaningful (may not be correct at this point)
- if header.Difficulty == nil || header.Difficulty.Cmp(istanbulcommon.DefaultDifficulty) != 0 {
- return istanbulcommon.ErrInvalidDifficulty
- }
- return e.verifyCascadingFields(chain, header, validators, parents)
- }
- func (e *Engine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, validators istanbul.ValidatorSet) (chan<- struct{}, <-chan error) {
- abort := make(chan struct{})
- results := make(chan error, len(headers))
- go func() {
- errored := false
- for i, header := range headers {
- var err error
- if errored {
- err = consensus.ErrUnknownAncestor
- } else {
- err = e.verifyHeader(chain, header, headers[:i], validators)
- }
- if err != nil {
- errored = true
- }
- select {
- case <-abort:
- return
- case results <- err:
- }
- }
- }()
- return abort, results
- }
- // 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 (e *Engine) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, validators istanbul.ValidatorSet, parents []*types.Header) error {
- // The genesis block is the always valid dead-end
- number := header.Number.Uint64()
- if number == 0 {
- return nil
- }
- // Check parent
- var parent *types.Header
- if len(parents) > 0 {
- parent = parents[len(parents)-1]
- } else {
- parent = chain.GetHeader(header.ParentHash, number-1)
- }
- // Ensure that the block's parent has right number and hash
- if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
- return consensus.ErrUnknownAncestor
- }
- // Ensure that the block's timestamp isn't too close to it's parent
- // When the BlockPeriod is reduced it is reduced for the proposal.
- // e.g when blockperiod is 1 from block 10 the block period between 9 and 10 is 1
- if parent.Time+e.cfg.GetConfig(header.Number).BlockPeriod > header.Time {
- return istanbulcommon.ErrInvalidTimestamp
- }
- // Verify signer
- if err := e.verifySigner(chain, header, parents, validators); err != nil {
- return err
- }
- return e.verifyCommittedSeals(chain, header, parents, validators)
- }
- func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, validators istanbul.ValidatorSet) error {
- // Verifying the genesis block is not supported
- number := header.Number.Uint64()
- if number == 0 {
- return istanbulcommon.ErrUnknownBlock
- }
- // Resolve the authorization key and check against signers
- signer, err := e.Author(header)
- if err != nil {
- return err
- }
- // Signer should be in the validator set of previous block's extraData.
- if _, v := validators.GetByAddress(signer); v == nil {
- return istanbulcommon.ErrUnauthorized
- }
- return nil
- }
- // verifyCommittedSeals checks whether every committed seal is signed by one of the parent's validators
- func (e *Engine) verifyCommittedSeals(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, validators istanbul.ValidatorSet) error {
- number := header.Number.Uint64()
- if number == 0 {
- // We don't need to verify committed seals in the genesis block
- return nil
- }
- extra, err := types.ExtractQBFTExtra(header)
- if err != nil {
- return err
- }
- committedSeal := extra.CommittedSeal
- // The length of Committed seals should be larger than 0
- if len(committedSeal) == 0 {
- return istanbulcommon.ErrEmptyCommittedSeals
- }
- validatorsCpy := validators.Copy()
- // Check whether the committed seals are generated by validators
- validSeal := 0
- committers, err := e.Signers(header)
- if err != nil {
- return err
- }
- for _, addr := range committers {
- if validatorsCpy.RemoveValidator(addr) {
- validSeal++
- continue
- }
- return istanbulcommon.ErrInvalidCommittedSeals
- }
- // The length of validSeal should be larger than number of faulty node + 1
- if validSeal <= validators.F() {
- return istanbulcommon.ErrInvalidCommittedSeals
- }
- return nil
- }
- // VerifyUncles verifies that the given block's uncles conform to the consensus
- // rules of a given engine.
- func (e *Engine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
- if len(block.Uncles()) > 0 {
- return istanbulcommon.ErrInvalidUncleHash
- }
- return nil
- }
- // VerifySeal checks whether the crypto seal on a header is valid according to
- // the consensus rules of the given engine.
- func (e *Engine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header, validators istanbul.ValidatorSet) error {
- // get parent header and ensure the signer is in parent's validator set
- number := header.Number.Uint64()
- if number == 0 {
- return istanbulcommon.ErrUnknownBlock
- }
- // ensure that the difficulty equals to istanbulcommon.DefaultDifficulty
- if header.Difficulty.Cmp(istanbulcommon.DefaultDifficulty) != 0 {
- return istanbulcommon.ErrInvalidDifficulty
- }
- return e.verifySigner(chain, header, nil, validators)
- }
- func (e *Engine) Prepare(chain consensus.ChainHeaderReader, header *types.Header, validators istanbul.ValidatorSet) error {
- header.Coinbase = common.Address{}
- header.Nonce = istanbulcommon.EmptyBlockNonce
- header.MixDigest = types.IstanbulDigest
- // copy the parent extra data as the header extra data
- number := header.Number.Uint64()
- parent := chain.GetHeader(header.ParentHash, number-1)
- if parent == nil {
- return consensus.ErrUnknownAncestor
- }
- // use the same difficulty for all blocks
- header.Difficulty = istanbulcommon.DefaultDifficulty
- // set header's timestamp
- header.Time = parent.Time + e.cfg.GetConfig(header.Number).BlockPeriod
- if header.Time < uint64(time.Now().Unix()) {
- header.Time = uint64(time.Now().Unix())
- }
- currentBlockNumber := big.NewInt(0).SetUint64(number - 1)
- validatorContract := e.cfg.GetValidatorContractAddress(currentBlockNumber)
- if validatorContract != (common.Address{}) && e.cfg.GetValidatorSelectionMode(currentBlockNumber) == params.ContractMode {
- return ApplyHeaderQBFTExtra(
- header,
- WriteValidators([]common.Address{}),
- )
- } else {
- for _, transition := range e.cfg.Transitions {
- if transition.Block.Cmp(currentBlockNumber) == 0 && len(transition.Validators) > 0 {
- toRemove := make([]istanbul.Validator, 0, validators.Size())
- l := validators.List()
- for i := range l {
- toRemove = append(toRemove, l[i])
- }
- for i := range toRemove {
- validators.RemoveValidator(toRemove[i].Address())
- }
- for i := range transition.Validators {
- validators.AddValidator(transition.Validators[i])
- }
- break
- }
- }
- validatorsList := validator.SortedAddresses(validators.List())
- // add validators in snapshot to extraData's validators section
- return ApplyHeaderQBFTExtra(
- header,
- WriteValidators(validatorsList),
- )
- }
- }
- func WriteValidators(validators []common.Address) ApplyQBFTExtra {
- return func(qbftExtra *types.QBFTExtra) error {
- qbftExtra.Validators = validators
- return nil
- }
- }
- // Finalize runs any post-transaction state modifications (e.g. block rewards)
- // and assembles the final block.
- //
- // Note, the block header and state database might be updated to reflect any
- // consensus rules that happen at finalization (e.g. block rewards).
- func (e *Engine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
- // Accumulate any block and uncle rewards and commit the final state root
- e.accumulateRewards(chain, state, header)
- header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
- header.UncleHash = nilUncleHash
- }
- // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
- // nor block rewards given, and returns the final block.
- func (e *Engine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
- e.Finalize(chain, header, state, txs, uncles)
- // Assemble and return the final block for sealing
- return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil
- }
- // Seal generates a new block for the given input block with the local miner's
- // seal place on top.
- func (e *Engine) Seal(chain consensus.ChainHeaderReader, block *types.Block, validators istanbul.ValidatorSet) (*types.Block, error) {
- if _, v := validators.GetByAddress(e.signer); v == nil {
- return block, istanbulcommon.ErrUnauthorized
- }
- header := block.Header()
- parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
- if parent == nil {
- return block, consensus.ErrUnknownAncestor
- }
- // Set Coinbase
- header.Coinbase = e.signer
- return block.WithSeal(header), nil
- }
- func (e *Engine) SealHash(header *types.Header) common.Hash {
- header.Coinbase = e.signer
- return sigHash(header)
- }
- func (e *Engine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
- return new(big.Int)
- }
- func (e *Engine) ExtractGenesisValidators(header *types.Header) ([]common.Address, error) {
- extra, err := types.ExtractQBFTExtra(header)
- if err != nil {
- return nil, err
- }
- return extra.Validators, nil
- }
- func (e *Engine) Signers(header *types.Header) ([]common.Address, error) {
- extra, err := types.ExtractQBFTExtra(header)
- if err != nil {
- return []common.Address{}, err
- }
- committedSeal := extra.CommittedSeal
- proposalSeal := PrepareCommittedSeal(header, extra.Round)
- var addrs []common.Address
- // 1. Get committed seals from current header
- for _, seal := range committedSeal {
- // 2. Get the original address by seal and parent block hash
- addr, err := istanbul.GetSignatureAddressNoHashing(proposalSeal, seal)
- if err != nil {
- return nil, istanbulcommon.ErrInvalidSignature
- }
- addrs = append(addrs, addr)
- }
- return addrs, nil
- }
- func (e *Engine) Address() common.Address {
- return e.signer
- }
- // FIXME: Need to update this for Istanbul
- // sigHash returns the hash which is used as input for the Istanbul
- // 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.NewLegacyKeccak256()
- rlp.Encode(hasher, types.QBFTFilteredHeader(header))
- hasher.Sum(hash[:0])
- return hash
- }
- // PrepareCommittedSeal returns a committed seal for the given hash
- func PrepareCommittedSeal(header *types.Header, round uint32) []byte {
- h := types.CopyHeader(header)
- return h.QBFTHashWithRoundNumber(round).Bytes()
- }
- func (e *Engine) WriteVote(header *types.Header, candidate common.Address, authorize bool) error {
- return ApplyHeaderQBFTExtra(
- header,
- WriteVote(candidate, authorize),
- )
- }
- func WriteVote(candidate common.Address, authorize bool) ApplyQBFTExtra {
- return func(qbftExtra *types.QBFTExtra) error {
- voteType := types.QBFTDropVote
- if authorize {
- voteType = types.QBFTAuthVote
- }
- vote := &types.ValidatorVote{RecipientAddress: candidate, VoteType: voteType}
- qbftExtra.Vote = vote
- return nil
- }
- }
- func (e *Engine) ReadVote(header *types.Header) (candidate common.Address, authorize bool, err error) {
- qbftExtra, err := getExtra(header)
- if err != nil {
- return common.Address{}, false, err
- }
- var vote *types.ValidatorVote
- if qbftExtra.Vote == nil {
- vote = &types.ValidatorVote{RecipientAddress: common.Address{}, VoteType: types.QBFTDropVote}
- } else {
- vote = qbftExtra.Vote
- }
- // Tally up the new vote from the validator
- switch {
- case vote.VoteType == types.QBFTAuthVote:
- authorize = true
- case vote.VoteType == types.QBFTDropVote:
- authorize = false
- default:
- return common.Address{}, false, istanbulcommon.ErrInvalidVote
- }
- return vote.RecipientAddress, authorize, nil
- }
- func getExtra(header *types.Header) (*types.QBFTExtra, error) {
- if len(header.Extra) < types.IstanbulExtraVanity {
- // In this scenario, the header extradata only contains client specific information, hence create a new qbftExtra and set vanity
- vanity := append(header.Extra, bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity-len(header.Extra))...)
- return &types.QBFTExtra{
- VanityData: vanity,
- Validators: []common.Address{},
- CommittedSeal: [][]byte{},
- Round: 0,
- Vote: nil,
- }, nil
- }
- // This is the case when Extra has already been set
- return types.ExtractQBFTExtra(header)
- }
- func setExtra(h *types.Header, qbftExtra *types.QBFTExtra) error {
- payload, err := rlp.EncodeToBytes(qbftExtra)
- if err != nil {
- return err
- }
- h.Extra = payload
- return nil
- }
- func (e *Engine) validatorsList(genesis *types.Header, config istanbul.Config) ([]common.Address, error) {
- var validators []common.Address
- if config.ValidatorContract != (common.Address{}) && config.GetValidatorSelectionMode(big.NewInt(0)) == params.ContractMode {
- log.Info("Initialising snap with contract validators", "address", config.ValidatorContract, "client", config.Client)
- validatorContractCaller, err := contract.NewValidatorContractInterfaceCaller(config.ValidatorContract, config.Client)
- if err != nil {
- return nil, fmt.Errorf("invalid smart contract in genesis alloc: %w", err)
- }
- opts := bind.CallOpts{
- Pending: false,
- BlockNumber: big.NewInt(0),
- }
- validators, err = validatorContractCaller.GetValidators(&opts)
- if err != nil {
- log.Error("QBFT: invalid smart contract in genesis alloc", "err", err)
- return nil, err
- }
- } else {
- // Get the validators from genesis to create a snapshot
- var err error
- validators, err = e.ExtractGenesisValidators(genesis)
- if err != nil {
- log.Error("BFT: invalid genesis block", "err", err)
- return nil, err
- }
- }
- return validators, nil
- }
- // AccumulateRewards credits the beneficiary of the given block with a reward.
- func (e *Engine) accumulateRewards(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header) {
- blockReward := chain.Config().GetBlockReward(header.Number)
- if blockReward.Cmp(big.NewInt(0)) > 0 {
- coinbase := header.Coinbase
- if (coinbase == common.Address{}) {
- coinbase = e.signer
- }
- rewardAccount, _ := chain.Config().GetRewardAccount(header.Number, coinbase)
- log.Trace("QBFT: accumulate rewards to", "rewardAccount", rewardAccount, "blockReward", blockReward)
- state.AddBalance(rewardAccount, &blockReward)
- }
- }
|