// 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 backend import ( "fmt" "math/big" "math/rand" "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/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) 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 inmemoryPeers = 40 inmemoryMessages = 1024 ) // Author retrieves the Ethereum address of the account that minted the given // block, which may be different from the header's coinbase if a consensus // engine is based on signatures. func (sb *Backend) Author(header *types.Header) (common.Address, error) { return sb.EngineForBlockNumber(header.Number).Author(header) } // Signers extracts all the addresses who have signed the given header // It will extract for each seal who signed it, regardless of if the seal is // repeated func (sb *Backend) Signers(header *types.Header) ([]common.Address, error) { return sb.EngineForBlockNumber(header.Number).Signers(header) } // VerifyHeader checks whether a header conforms to the consensus rules of a // given engine. Verifying the seal may be done optionally here, or explicitly // via the VerifySeal method. func (sb *Backend) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { return sb.verifyHeader(chain, header, nil) } func (sb *Backend) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { // Assemble the voting snapshot snap, err := sb.snapshot(chain, header.Number.Uint64()-1, header.ParentHash, parents) if err != nil { return err } return sb.EngineForBlockNumber(header.Number).VerifyHeader(chain, header, parents, snap.ValSet) } // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers // concurrently. 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 (sb *Backend) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (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 = sb.verifyHeader(chain, header, headers[:i]) } if err != nil { errored = true } select { case <-abort: return case results <- err: } } }() return abort, results } // VerifyUncles verifies that the given block's uncles conform to the consensus // rules of a given engine. func (sb *Backend) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { return sb.EngineForBlockNumber(block.Header().Number).VerifyUncles(chain, block) } // VerifySeal checks whether the crypto seal on a header is valid according to // the consensus rules of the given engine. func (sb *Backend) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { // get parent header and ensure the signer is in parent's validator set number := header.Number.Uint64() if number == 0 { return istanbulcommon.ErrUnknownBlock } // Assemble the voting snapshot snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil) if err != nil { return err } return sb.EngineForBlockNumber(header.Number).VerifySeal(chain, header, snap.ValSet) } // Prepare initializes the consensus fields of a block header according to the // rules of a particular engine. The changes are executed inline. func (sb *Backend) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { // Assemble the voting snapshot snap, err := sb.snapshot(chain, header.Number.Uint64()-1, header.ParentHash, nil) if err != nil { return err } err = sb.EngineForBlockNumber(header.Number).Prepare(chain, header, snap.ValSet) if err != nil { return err } // get valid candidate list sb.candidatesLock.RLock() var addresses []common.Address var authorizes []bool for address, authorize := range sb.candidates { if snap.checkVote(address, authorize) { addresses = append(addresses, address) authorizes = append(authorizes, authorize) } } sb.candidatesLock.RUnlock() if len(addresses) > 0 { index := rand.Intn(len(addresses)) err = sb.EngineForBlockNumber(header.Number).WriteVote(header, addresses[index], authorizes[index]) if err != nil { log.Error("BFT: error writing validator vote", "err", err) return err } } 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 (sb *Backend) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { sb.EngineForBlockNumber(header.Number).Finalize(chain, header, state, txs, uncles) } // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, // nor block rewards given, and returns the final block. func (sb *Backend) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { return sb.EngineForBlockNumber(header.Number).FinalizeAndAssemble(chain, header, state, txs, uncles, receipts) } // Seal generates a new block for the given input block with the local miner's // seal place on top. func (sb *Backend) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { // update the block header timestamp and signature and propose the block to core engine header := block.Header() number := header.Number.Uint64() // Bail out if we're unauthorized to sign a block snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil) if err != nil { return err } block, err = sb.EngineForBlockNumber(header.Number).Seal(chain, block, snap.ValSet) if err != nil { return err } delay := time.Until(time.Unix(int64(block.Header().Time), 0)) go func() { // wait for the timestamp of header, use this to adjust the block period select { case <-time.After(delay): case <-stop: results <- nil return } // get the proposed block hash and clear it if the seal() is completed. sb.sealMu.Lock() sb.proposedBlockHash = block.Hash() defer func() { sb.proposedBlockHash = common.Hash{} sb.sealMu.Unlock() }() // post block into Istanbul engine go sb.EventMux().Post(istanbul.RequestEvent{ Proposal: block, }) for { select { case result := <-sb.commitCh: // if the block hash and the hash from channel are the same, // return the result. Otherwise, keep waiting the next hash. if result != nil && block.Hash() == result.Hash() { results <- result return } case <-stop: results <- nil return } } }() return nil } // APIs returns the RPC APIs this consensus engine provides. func (sb *Backend) APIs(chain consensus.ChainHeaderReader) []rpc.API { return []rpc.API{{ Namespace: "istanbul", Version: "1.0", Service: &API{chain: chain, backend: sb}, Public: true, }} } // Start implements consensus.Istanbul.Start func (sb *Backend) Start(chain consensus.ChainHeaderReader, currentBlock func() *types.Block, hasBadBlock func(db ethdb.Reader, hash common.Hash) bool) error { sb.coreMu.Lock() defer sb.coreMu.Unlock() if sb.coreStarted { return istanbul.ErrStartedEngine } // clear previous data sb.proposedBlockHash = common.Hash{} if sb.commitCh != nil { close(sb.commitCh) } sb.commitCh = make(chan *types.Block, 1) sb.chain = chain sb.currentBlock = currentBlock sb.hasBadBlock = hasBadBlock // Check if qbft Consensus needs to be used after chain is set var err error if sb.IsQBFTConsensus() { log.Info("start QBFT") err = sb.startQBFT() } else { log.Info("start IBFT") err = sb.startIBFT() } if err != nil { return err } sb.coreStarted = true return nil } // Stop implements consensus.Istanbul.Stop func (sb *Backend) Stop() error { sb.coreMu.Lock() defer sb.coreMu.Unlock() if !sb.coreStarted { return istanbul.ErrStoppedEngine } if err := sb.stop(); err != nil { return err } sb.coreStarted = false return nil } func addrsToString(addrs []common.Address) []string { strs := make([]string, len(addrs)) for i, addr := range addrs { strs[i] = addr.String() } return strs } func (sb *Backend) snapLogger(snap *Snapshot) log.Logger { return sb.logger.New( "snap.number", snap.Number, "snap.hash", snap.Hash.String(), "snap.epoch", snap.Epoch, "snap.validators", addrsToString(snap.validators()), "snap.votes", snap.Votes, ) } func (sb *Backend) storeSnap(snap *Snapshot) error { logger := sb.snapLogger(snap) logger.Debug("BFT: store snapshot to database") if err := snap.store(sb.db); err != nil { logger.Error("BFT: failed to store snapshot to database", "err", err) return err } return nil } // snapshot retrieves the authorization snapshot at a given point in time. func (sb *Backend) snapshot(chain consensus.ChainHeaderReader, 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 := sb.recents.Get(hash); ok { snap = s.(*Snapshot) sb.snapLogger(snap).Trace("BFT: loaded voting snapshot from cache") break } // If an on-disk checkpoint snapshot can be found, use that if number%checkpointInterval == 0 { if s, err := loadSnapshot(sb.config.GetConfig(new(big.Int).SetUint64(number)).Epoch, sb.db, hash); err == nil { snap = s sb.snapLogger(snap).Trace("BFT: loaded voting snapshot from database") break } } // If we're at block zero, make a snapshot if number == 0 { genesis := chain.GetHeaderByNumber(0) if err := sb.EngineForBlockNumber(big.NewInt(0)).VerifyHeader(chain, genesis, nil, nil); err != nil { sb.logger.Error("BFT: invalid genesis block", "err", err) return nil, err } var validators []common.Address validatorContract := sb.config.GetValidatorContractAddress(big.NewInt(0)) if validatorContract != (common.Address{}) && sb.config.GetValidatorSelectionMode(big.NewInt(0)) == params.ContractMode { validatorContractCaller, err := contract.NewValidatorContractInterfaceCaller(validatorContract, sb.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) log.Trace("BFT: Initialising snap with contract validators", "address", validatorContract, "validators", validators) if err != nil { log.Error("BFT: invalid smart contract in genesis alloc", "err", err) return nil, err } } else { validatorsFromConfig := sb.config.GetValidatorsAt(big.NewInt(0)) if len(validatorsFromConfig) > 0 { validators = validatorsFromConfig log.Info("BFT: Initialising snap with config validators", "validators", validators) } else { var err error validators, err = sb.EngineForBlockNumber(big.NewInt(0)).ExtractGenesisValidators(genesis) log.Info("BFT: Initialising snap with extradata", "validators", validators) if err != nil { log.Error("BFT: invalid genesis block", "err", err) return nil, err } } } snap = newSnapshot(sb.config.GetConfig(new(big.Int).SetUint64(number)).Epoch, 0, genesis.Hash(), validator.NewSet(validators, sb.config.ProposerPolicy)) if err := sb.storeSnap(snap); err != nil { return nil, err } 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 := sb.snapApply(snap, headers) if err != nil { return nil, err } sb.recents.Add(snap.Hash, snap) targetBlockHeight := new(big.Int).SetUint64(number) validatorContract := sb.config.GetValidatorContractAddress(targetBlockHeight) // we only need to update the validator set if it's a new block if len(headers) == 0 && validatorContract != (common.Address{}) && sb.config.GetValidatorSelectionMode(targetBlockHeight) == params.ContractMode { sb.logger.Trace("Applying snap with smart contract validators", "address", validatorContract, "client", sb.config.Client) validatorContractCaller, err := contract.NewValidatorContractInterfaceCaller(validatorContract, sb.config.Client) if err != nil { return nil, fmt.Errorf("BFT: invalid smart contract in genesis alloc: %w", err) } opts := bind.CallOpts{ Pending: false, BlockNumber: targetBlockHeight, } validators, err := validatorContractCaller.GetValidators(&opts) if err != nil { log.Error("BFT: invalid validator smart contract", "err", err) return nil, err } sb.logger.Trace("Fetched validators from smart contract", "validators", validators) valSet := validator.NewSet(validators, sb.config.ProposerPolicy) snap.ValSet = valSet } else if validatorsFromTransitions := sb.config.GetValidatorsAt(targetBlockHeight); len(validatorsFromTransitions) > 0 && sb.config.GetValidatorSelectionMode(targetBlockHeight) == params.BlockHeaderMode { //Note! we only want to set this once at this block height. Subsequent blocks will be propagated with the same // validator as they are copied into the block header on the next block. Then normal voting can take place // again. valSet := validator.NewSet(validatorsFromTransitions, sb.config.ProposerPolicy) snap.ValSet = valSet } // If we've generated a new checkpoint snapshot, save to disk if snap.Number%checkpointInterval == 0 && len(headers) > 0 { if err = sb.storeSnap(snap); err != nil { return nil, err } } return snap, err } // SealHash returns the hash of a block prior to it being sealed. func (sb *Backend) SealHash(header *types.Header) common.Hash { return sb.EngineForBlockNumber(header.Number).SealHash(header) } func (sb *Backend) snapApply(snap *Snapshot, headers []*types.Header) (*Snapshot, error) { // Allow passing in no headers for cleaner code if len(headers) == 0 { return snap, 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, istanbulcommon.ErrInvalidVotingChain } } if headers[0].Number.Uint64() != snap.Number+1 { return nil, istanbulcommon.ErrInvalidVotingChain } // Iterate through the headers and create a new snapshot snapCpy := snap.copy() for _, header := range headers { err := sb.snapApplyHeader(snapCpy, header) if err != nil { return nil, err } } snapCpy.Number += uint64(len(headers)) snapCpy.Hash = headers[len(headers)-1].Hash() return snapCpy, nil } func (sb *Backend) snapApplyHeader(snap *Snapshot, header *types.Header) error { logger := sb.snapLogger(snap).New("header.number", header.Number.Uint64(), "header.hash", header.Hash().String()) logger.Trace("BFT: apply header to voting snapshot") // Remove any votes on checkpoint blocks number := header.Number.Uint64() if number%snap.Epoch == 0 { snap.Votes = nil snap.Tally = make(map[common.Address]Tally) } // Resolve the authorization key and check against validators validator, err := sb.EngineForBlockNumber(header.Number).Author(header) if err != nil { logger.Error("BFT: invalid header author", "err", err) return err } logger = logger.New("header.author", validator) if _, v := snap.ValSet.GetByAddress(validator); v == nil { logger.Error("BFT: header author is not a validator", "Validators", snap.ValSet, "Author", validator) return istanbulcommon.ErrUnauthorized } // Read vote from header candidate, authorize, err := sb.EngineForBlockNumber(header.Number).ReadVote(header) if err != nil { logger.Error("BFT: invalid header vote", "err", err) return err } logger = logger.New("candidate", candidate.String(), "authorize", authorize) // Header authorized, discard any previous votes from the validator for i, vote := range snap.Votes { if vote.Validator == validator && vote.Address == candidate { logger.Trace("BFT: discard previous vote from tally", "old.authorize", vote.Authorize) // 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 } } logger.Debug("BFT: add vote to tally") if snap.cast(candidate, authorize) { snap.Votes = append(snap.Votes, &Vote{ Validator: validator, Block: number, Address: candidate, Authorize: authorize, }) } // If the vote passed, update the list of validators if tally := snap.Tally[candidate]; tally.Votes > snap.ValSet.Size()/2 { if tally.Authorize { logger.Info("BFT: reached majority to add validator") snap.ValSet.AddValidator(candidate) } else { logger.Info("BFT: reached majority to remove validator") snap.ValSet.RemoveValidator(candidate) // Discard any previous votes the deauthorized validator cast for i := 0; i < len(snap.Votes); i++ { if snap.Votes[i].Validator == candidate { // 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 == candidate { snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) i-- } } delete(snap.Tally, candidate) } return nil }