quorum/consensus/istanbul/core/roundstate.go

222 lines
5.3 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 core
import (
"io"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/istanbul"
"github.com/ethereum/go-ethereum/rlp"
)
// newRoundState creates a new roundState instance with the given view and validatorSet
// lockedHash and preprepare are for round change when lock exists,
// we need to keep a reference of preprepare in order to propose locked proposal when there is a lock and itself is the proposer
func newRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, lockedHash common.Hash, preprepare *istanbul.Preprepare, pendingRequest *istanbul.Request, hasBadProposal func(hash common.Hash) bool) *roundState {
return &roundState{
round: view.Round,
sequence: view.Sequence,
Preprepare: preprepare,
Prepares: newMessageSet(validatorSet),
Commits: newMessageSet(validatorSet),
lockedHash: lockedHash,
mu: new(sync.RWMutex),
pendingRequest: pendingRequest,
hasBadProposal: hasBadProposal,
}
}
// roundState stores the consensus state
type roundState struct {
round *big.Int
sequence *big.Int
Preprepare *istanbul.Preprepare
Prepares *messageSet
Commits *messageSet
lockedHash common.Hash
pendingRequest *istanbul.Request
mu *sync.RWMutex
hasBadProposal func(hash common.Hash) bool
}
func (s *roundState) GetPrepareOrCommitSize() int {
s.mu.RLock()
defer s.mu.RUnlock()
result := s.Prepares.Size() + s.Commits.Size()
// find duplicate one
for _, m := range s.Prepares.Values() {
if s.Commits.Get(m.Address) != nil {
result--
}
}
return result
}
func (s *roundState) Subject() *istanbul.Subject {
s.mu.RLock()
defer s.mu.RUnlock()
if s.Preprepare == nil {
return nil
}
return &istanbul.Subject{
View: &istanbul.View{
Round: new(big.Int).Set(s.round),
Sequence: new(big.Int).Set(s.sequence),
},
Digest: s.Preprepare.Proposal.Hash(),
}
}
func (s *roundState) SetPreprepare(preprepare *istanbul.Preprepare) {
s.mu.Lock()
defer s.mu.Unlock()
s.Preprepare = preprepare
}
func (s *roundState) Proposal() istanbul.Proposal {
s.mu.RLock()
defer s.mu.RUnlock()
if s.Preprepare != nil {
return s.Preprepare.Proposal
}
return nil
}
func (s *roundState) SetRound(r *big.Int) {
s.mu.Lock()
defer s.mu.Unlock()
s.round = new(big.Int).Set(r)
}
func (s *roundState) Round() *big.Int {
s.mu.RLock()
defer s.mu.RUnlock()
return s.round
}
func (s *roundState) SetSequence(seq *big.Int) {
s.mu.Lock()
defer s.mu.Unlock()
s.sequence = seq
}
func (s *roundState) Sequence() *big.Int {
s.mu.RLock()
defer s.mu.RUnlock()
return s.sequence
}
func (s *roundState) LockHash() {
s.mu.Lock()
defer s.mu.Unlock()
if s.Preprepare != nil {
s.lockedHash = s.Preprepare.Proposal.Hash()
}
}
func (s *roundState) UnlockHash() {
s.mu.Lock()
defer s.mu.Unlock()
s.lockedHash = common.Hash{}
}
func (s *roundState) IsHashLocked() bool {
s.mu.RLock()
defer s.mu.RUnlock()
if common.EmptyHash(s.lockedHash) {
return false
}
return !s.hasBadProposal(s.GetLockedHash())
}
func (s *roundState) GetLockedHash() common.Hash {
s.mu.RLock()
defer s.mu.RUnlock()
return s.lockedHash
}
// The DecodeRLP method should read one value from the given
// Stream. It is not forbidden to read less or more, but it might
// be confusing.
func (s *roundState) DecodeRLP(stream *rlp.Stream) error {
var ss struct {
Round *big.Int
Sequence *big.Int
Preprepare *istanbul.Preprepare
Prepares *messageSet
Commits *messageSet
lockedHash common.Hash
pendingRequest *istanbul.Request
}
if err := stream.Decode(&ss); err != nil {
return err
}
s.round = ss.Round
s.sequence = ss.Sequence
s.Preprepare = ss.Preprepare
s.Prepares = ss.Prepares
s.Commits = ss.Commits
s.lockedHash = ss.lockedHash
s.pendingRequest = ss.pendingRequest
s.mu = new(sync.RWMutex)
return nil
}
// EncodeRLP should write the RLP encoding of its receiver to w.
// If the implementation is a pointer method, it may also be
// called for nil pointers.
//
// Implementations should generate valid RLP. The data written is
// not verified at the moment, but a future version might. It is
// recommended to write only a single value but writing multiple
// values or no value at all is also permitted.
func (s *roundState) EncodeRLP(w io.Writer) error {
s.mu.RLock()
defer s.mu.RUnlock()
return rlp.Encode(w, []interface{}{
s.round,
s.sequence,
s.Preprepare,
s.Prepares,
s.Commits,
s.lockedHash,
s.pendingRequest,
})
}