mirror of https://github.com/poanetwork/quorum.git
323 lines
9.6 KiB
Go
323 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(block.Header().Time.Int64(), 0).Sub(now()), consensus.ErrFutureBlock
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
// Sign implements istanbul.Backend.Sign
|
|
func (sb *backend) Sign(data []byte) ([]byte, error) {
|
|
hashData := crypto.Keccak256([]byte(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
|
|
} |