quorum/consensus/istanbul/backend/backend.go

320 lines
9.6 KiB
Go

// 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 <http://www.gnu.org/licenses/>.
package backend
import (
"crypto/ecdsa"
"math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/istanbul"
istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core"
"github.com/ethereum/go-ethereum/consensus/istanbul/validator"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
lru "github.com/hashicorp/golang-lru"
)
const (
// fetcherID is the ID indicates the block is from Istanbul engine
fetcherID = "istanbul"
)
// New creates an Ethereum backend for Istanbul core engine.
func New(config *istanbul.Config, privateKey *ecdsa.PrivateKey, db ethdb.Database) consensus.Istanbul {
// Allocate the snapshot caches and create the engine
recents, _ := lru.NewARC(inmemorySnapshots)
recentMessages, _ := lru.NewARC(inmemoryPeers)
knownMessages, _ := lru.NewARC(inmemoryMessages)
backend := &backend{
config: config,
istanbulEventMux: new(event.TypeMux),
privateKey: privateKey,
address: crypto.PubkeyToAddress(privateKey.PublicKey),
logger: log.New(),
db: db,
commitCh: make(chan *types.Block, 1),
recents: recents,
candidates: make(map[common.Address]bool),
coreStarted: false,
recentMessages: recentMessages,
knownMessages: knownMessages,
}
backend.core = istanbulCore.New(backend, backend.config)
return backend
}
// ----------------------------------------------------------------------------
type backend struct {
config *istanbul.Config
istanbulEventMux *event.TypeMux
privateKey *ecdsa.PrivateKey
address common.Address
core istanbulCore.Engine
logger log.Logger
db ethdb.Database
chain consensus.ChainReader
currentBlock func() *types.Block
hasBadBlock func(hash common.Hash) bool
// the channels for istanbul engine notifications
commitCh chan *types.Block
proposedBlockHash common.Hash
sealMu sync.Mutex
coreStarted bool
coreMu sync.RWMutex
// Current list of candidates we are pushing
candidates map[common.Address]bool
// Protects the signer fields
candidatesLock sync.RWMutex
// Snapshots for recent block to speed up reorgs
recents *lru.ARCCache
// event subscription for ChainHeadEvent event
broadcaster consensus.Broadcaster
recentMessages *lru.ARCCache // the cache of peer's messages
knownMessages *lru.ARCCache // the cache of self messages
}
// zekun: HACK
func (sb *backend) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
return new(big.Int)
}
// Address implements istanbul.Backend.Address
func (sb *backend) Address() common.Address {
return sb.address
}
// Validators implements istanbul.Backend.Validators
func (sb *backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet {
return sb.getValidators(proposal.Number().Uint64(), proposal.Hash())
}
// Broadcast implements istanbul.Backend.Broadcast
func (sb *backend) Broadcast(valSet istanbul.ValidatorSet, payload []byte) error {
// send to others
sb.Gossip(valSet, payload)
// send to self
msg := istanbul.MessageEvent{
Payload: payload,
}
go sb.istanbulEventMux.Post(msg)
return nil
}
// Broadcast implements istanbul.Backend.Gossip
func (sb *backend) Gossip(valSet istanbul.ValidatorSet, payload []byte) error {
hash := istanbul.RLPHash(payload)
sb.knownMessages.Add(hash, true)
targets := make(map[common.Address]bool)
for _, val := range valSet.List() {
if val.Address() != sb.Address() {
targets[val.Address()] = true
}
}
if sb.broadcaster != nil && len(targets) > 0 {
ps := sb.broadcaster.FindPeers(targets)
for addr, p := range ps {
ms, ok := sb.recentMessages.Get(addr)
var m *lru.ARCCache
if ok {
m, _ = ms.(*lru.ARCCache)
if _, k := m.Get(hash); k {
// This peer had this event, skip it
continue
}
} else {
m, _ = lru.NewARC(inmemoryMessages)
}
m.Add(hash, true)
sb.recentMessages.Add(addr, m)
go p.Send(istanbulMsg, payload)
}
}
return nil
}
// Commit implements istanbul.Backend.Commit
func (sb *backend) Commit(proposal istanbul.Proposal, seals [][]byte) error {
// Check if the proposal is a valid block
block := &types.Block{}
block, ok := proposal.(*types.Block)
if !ok {
sb.logger.Error("Invalid proposal, %v", proposal)
return errInvalidProposal
}
h := block.Header()
// Append seals into extra-data
err := writeCommittedSeals(h, seals)
if err != nil {
return err
}
// update block's header
block = block.WithSeal(h)
sb.logger.Info("Committed", "address", sb.Address(), "hash", proposal.Hash(), "number", proposal.Number().Uint64())
// - if the proposed and committed blocks are the same, send the proposed hash
// to commit channel, which is being watched inside the engine.Seal() function.
// - otherwise, we try to insert the block.
// -- if success, the ChainHeadEvent event will be broadcasted, try to build
// the next block and the previous Seal() will be stopped.
// -- otherwise, a error will be returned and a round change event will be fired.
if sb.proposedBlockHash == block.Hash() {
// feed block hash to Seal() and wait the Seal() result
sb.commitCh <- block
return nil
}
if sb.broadcaster != nil {
sb.broadcaster.Enqueue(fetcherID, block)
}
return nil
}
// EventMux implements istanbul.Backend.EventMux
func (sb *backend) EventMux() *event.TypeMux {
return sb.istanbulEventMux
}
// Verify implements istanbul.Backend.Verify
func (sb *backend) Verify(proposal istanbul.Proposal) (time.Duration, error) {
// Check if the proposal is a valid block
block := &types.Block{}
block, ok := proposal.(*types.Block)
if !ok {
sb.logger.Error("Invalid proposal, %v", proposal)
return 0, errInvalidProposal
}
// check bad block
if sb.HasBadProposal(block.Hash()) {
return 0, core.ErrBlacklistedHash
}
// check block body
txnHash := types.DeriveSha(block.Transactions())
uncleHash := types.CalcUncleHash(block.Uncles())
if txnHash != block.Header().TxHash {
return 0, errMismatchTxhashes
}
if uncleHash != nilUncleHash {
return 0, errInvalidUncleHash
}
// verify the header of proposed block
err := sb.VerifyHeader(sb.chain, block.Header(), false)
// ignore errEmptyCommittedSeals error because we don't have the committed seals yet
if err == nil || err == errEmptyCommittedSeals {
return 0, nil
} else if err == consensus.ErrFutureBlock {
return time.Unix(int64(block.Header().Time), 0).Sub(now()), consensus.ErrFutureBlock
}
return 0, err
}
// Sign implements istanbul.Backend.Sign
func (sb *backend) Sign(data []byte) ([]byte, error) {
hashData := crypto.Keccak256(data)
return crypto.Sign(hashData, sb.privateKey)
}
// CheckSignature implements istanbul.Backend.CheckSignature
func (sb *backend) CheckSignature(data []byte, address common.Address, sig []byte) error {
signer, err := istanbul.GetSignatureAddress(data, sig)
if err != nil {
log.Error("Failed to get signer address", "err", err)
return err
}
// Compare derived addresses
if signer != address {
return errInvalidSignature
}
return nil
}
// HasPropsal implements istanbul.Backend.HashBlock
func (sb *backend) HasPropsal(hash common.Hash, number *big.Int) bool {
return sb.chain.GetHeader(hash, number.Uint64()) != nil
}
// GetProposer implements istanbul.Backend.GetProposer
func (sb *backend) GetProposer(number uint64) common.Address {
if h := sb.chain.GetHeaderByNumber(number); h != nil {
a, _ := sb.Author(h)
return a
}
return common.Address{}
}
// ParentValidators implements istanbul.Backend.GetParentValidators
func (sb *backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet {
if block, ok := proposal.(*types.Block); ok {
return sb.getValidators(block.Number().Uint64()-1, block.ParentHash())
}
return validator.NewSet(nil, sb.config.ProposerPolicy)
}
func (sb *backend) getValidators(number uint64, hash common.Hash) istanbul.ValidatorSet {
snap, err := sb.snapshot(sb.chain, number, hash, nil)
if err != nil {
return validator.NewSet(nil, sb.config.ProposerPolicy)
}
return snap.ValSet
}
func (sb *backend) LastProposal() (istanbul.Proposal, common.Address) {
block := sb.currentBlock()
var proposer common.Address
if block.Number().Cmp(common.Big0) > 0 {
var err error
proposer, err = sb.Author(block.Header())
if err != nil {
sb.logger.Error("Failed to get block proposer", "err", err)
return nil, common.Address{}
}
}
// Return header only block here since we don't need block body
return block, proposer
}
func (sb *backend) HasBadProposal(hash common.Hash) bool {
if sb.hasBadBlock == nil {
return false
}
return sb.hasBadBlock(hash)
}
func (sb *backend) Close() error {
return nil
}