tendermint/consensus/reactor.go

1010 lines
29 KiB
Go
Raw Normal View History

2014-08-10 16:35:08 -07:00
package consensus
import (
"bytes"
"errors"
"fmt"
"reflect"
2014-08-10 16:35:08 -07:00
"sync"
"time"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-p2p"
2015-11-10 13:10:43 -08:00
"github.com/tendermint/go-wire"
bc "github.com/tendermint/tendermint/blockchain"
"github.com/tendermint/tendermint/events"
2015-04-01 17:30:16 -07:00
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
2014-08-10 16:35:08 -07:00
)
const (
StateChannel = byte(0x20)
DataChannel = byte(0x21)
VoteChannel = byte(0x22)
2014-09-14 15:37:32 -07:00
2015-07-09 21:46:15 -07:00
peerGossipSleepDuration = 100 * time.Millisecond // Time to sleep if there's nothing to send.
2015-11-10 13:10:43 -08:00
maxConsensusMessageSize = 1048576 // 1MB; NOTE: keep in sync with types.PartSet sizes.
2014-08-10 16:35:08 -07:00
)
//-----------------------------------------------------------------------------
2014-09-14 15:37:32 -07:00
type ConsensusReactor struct {
p2p.BaseReactor
2014-08-10 16:35:08 -07:00
blockStore *bc.BlockStore
conS *ConsensusState
2015-07-09 21:46:15 -07:00
fastSync bool
evsw events.Fireable
2014-08-10 16:35:08 -07:00
}
2015-07-09 21:46:15 -07:00
func NewConsensusReactor(consensusState *ConsensusState, blockStore *bc.BlockStore, fastSync bool) *ConsensusReactor {
2014-09-14 15:37:32 -07:00
conR := &ConsensusReactor{
2015-06-19 15:30:10 -07:00
blockStore: blockStore,
2015-01-11 14:27:46 -08:00
conS: consensusState,
2015-07-09 21:46:15 -07:00
fastSync: fastSync,
2014-08-10 16:35:08 -07:00
}
conR.BaseReactor = *p2p.NewBaseReactor(log, "ConsensusReactor", conR)
2014-09-14 15:37:32 -07:00
return conR
2014-08-10 16:35:08 -07:00
}
func (conR *ConsensusReactor) OnStart() error {
log.Notice("ConsensusReactor ", "fastSync", conR.fastSync)
2015-07-21 18:31:01 -07:00
conR.BaseReactor.OnStart()
if !conR.fastSync {
_, err := conR.conS.Start()
if err != nil {
return err
}
2014-09-14 15:37:32 -07:00
}
go conR.broadcastNewRoundStepRoutine()
return nil
}
2015-07-21 18:31:01 -07:00
func (conR *ConsensusReactor) OnStop() {
conR.BaseReactor.OnStop()
conR.conS.Stop()
2014-08-10 16:35:08 -07:00
}
// Switch from the fast_sync to the consensus:
// reset the state, turn off fast_sync, start the consensus-state-machine
func (conR *ConsensusReactor) SwitchToConsensus(state *sm.State) {
log.Notice("SwitchToConsensus")
// NOTE: The line below causes broadcastNewRoundStepRoutine() to
// broadcast a NewRoundStepMessage.
2015-09-15 13:13:39 -07:00
conR.conS.updateToState(state)
conR.fastSync = false
conR.conS.Start()
2014-10-30 03:32:09 -07:00
}
2014-09-14 15:37:32 -07:00
// Implements Reactor
func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor {
// TODO optimize
return []*p2p.ChannelDescriptor{
&p2p.ChannelDescriptor{
ID: StateChannel,
2015-05-05 17:03:11 -07:00
Priority: 5,
SendQueueCapacity: 100,
2014-09-14 15:37:32 -07:00
},
&p2p.ChannelDescriptor{
2015-12-09 13:53:31 -08:00
ID: DataChannel,
Priority: 2,
SendQueueCapacity: 50,
RecvBufferCapacity: 50 * 4096,
2014-09-14 15:37:32 -07:00
},
&p2p.ChannelDescriptor{
2015-12-09 13:53:31 -08:00
ID: VoteChannel,
Priority: 5,
SendQueueCapacity: 100,
RecvBufferCapacity: 100 * 100,
2014-09-14 15:37:32 -07:00
},
2014-08-10 16:35:08 -07:00
}
}
2014-09-14 15:37:32 -07:00
// Implements Reactor
func (conR *ConsensusReactor) AddPeer(peer *p2p.Peer) {
if !conR.IsRunning() {
return
}
2014-09-14 15:37:32 -07:00
// Create peerState for peer
peerState := NewPeerState(peer)
2015-09-25 09:55:59 -07:00
peer.Data.Set(types.PeerStateKey, peerState)
2014-09-14 15:37:32 -07:00
// Begin gossip routines for this peer.
go conR.gossipDataRoutine(peer, peerState)
go conR.gossipVotesRoutine(peer, peerState)
// Send our state to peer.
2015-07-09 21:46:15 -07:00
// If we're fast_syncing, broadcast a RoundStepMessage later upon SwitchToConsensus().
if !conR.fastSync {
conR.sendNewRoundStepMessage(peer)
}
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
// Implements Reactor
func (conR *ConsensusReactor) RemovePeer(peer *p2p.Peer, reason interface{}) {
if !conR.IsRunning() {
return
}
2015-07-10 08:39:49 -07:00
// TODO
//peer.Data.Get(PeerStateKey).(*PeerState).Disconnect()
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
// Implements Reactor
// NOTE: We process these messages even when we're fast_syncing.
func (conR *ConsensusReactor) Receive(chID byte, peer *p2p.Peer, msgBytes []byte) {
if !conR.IsRunning() {
log.Debug("Receive", "channel", chID, "peer", peer, "bytes", msgBytes)
return
}
2014-08-10 16:35:08 -07:00
2015-08-26 15:56:34 -07:00
// Get peer states
2015-09-25 09:55:59 -07:00
ps := peer.Data.Get(types.PeerStateKey).(*PeerState)
2015-07-13 16:00:01 -07:00
_, msg, err := DecodeMessage(msgBytes)
2014-12-29 15:14:54 -08:00
if err != nil {
2015-09-15 13:13:39 -07:00
log.Warn("Error decoding message", "channel", chID, "peer", peer, "msg", msg, "error", err, "bytes", msgBytes)
2015-08-12 11:00:23 -07:00
// TODO punish peer?
2014-12-29 15:14:54 -08:00
return
}
2015-09-15 13:13:39 -07:00
log.Debug("Receive", "channel", chID, "peer", peer, "msg", msg)
2014-10-25 14:27:53 -07:00
switch chID {
case StateChannel:
2015-07-13 16:00:01 -07:00
switch msg := msg.(type) {
2014-09-14 15:37:32 -07:00
case *NewRoundStepMessage:
2015-08-26 15:56:34 -07:00
ps.ApplyNewRoundStepMessage(msg)
case *CommitStepMessage:
ps.ApplyCommitStepMessage(msg)
case *HasVoteMessage:
ps.ApplyHasVoteMessage(msg)
2014-09-14 15:37:32 -07:00
default:
log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg)))
2014-09-14 15:37:32 -07:00
}
2014-08-10 16:35:08 -07:00
case DataChannel:
2015-07-09 21:46:15 -07:00
if conR.fastSync {
2015-07-13 16:00:01 -07:00
log.Warn("Ignoring message received during fastSync", "msg", msg)
2015-07-09 21:46:15 -07:00
return
}
2015-07-13 16:00:01 -07:00
switch msg := msg.(type) {
case *ProposalMessage:
ps.SetHasProposal(msg.Proposal)
err = conR.conS.SetProposal(msg.Proposal)
2015-06-22 19:04:31 -07:00
case *ProposalPOLMessage:
ps.ApplyProposalPOLMessage(msg)
case *BlockPartMessage:
ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Proof.Index)
2015-06-25 14:05:18 -07:00
_, err = conR.conS.AddProposalBlockPart(msg.Height, msg.Part)
2014-09-14 15:37:32 -07:00
default:
log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg)))
2014-09-14 15:37:32 -07:00
}
case VoteChannel:
2015-07-09 21:46:15 -07:00
if conR.fastSync {
2015-07-13 16:00:01 -07:00
log.Warn("Ignoring message received during fastSync", "msg", msg)
2015-07-09 21:46:15 -07:00
return
}
2015-07-13 16:00:01 -07:00
switch msg := msg.(type) {
case *VoteMessage:
2015-08-12 11:00:23 -07:00
vote, valIndex := msg.Vote, msg.ValidatorIndex
// attempt to add the vote and dupeout the validator if its a duplicate signature
2015-08-26 15:56:34 -07:00
added, err := conR.conS.TryAddVote(valIndex, vote, peer.Key)
2015-08-12 11:00:23 -07:00
if err == ErrAddingVote {
// TODO: punish peer
} else if err != nil {
return
}
2015-08-26 15:56:34 -07:00
cs := conR.conS
cs.mtx.Lock()
height, valSize, lastCommitSize := cs.Height, cs.Validators.Size(), cs.LastCommit.Size()
cs.mtx.Unlock()
ps.EnsureVoteBitArrays(height, valSize)
ps.EnsureVoteBitArrays(height-1, lastCommitSize)
2015-08-12 11:00:23 -07:00
ps.SetHasVote(vote, valIndex)
2014-09-14 15:37:32 -07:00
if added {
2015-06-22 19:04:31 -07:00
// If rs.Height == vote.Height && rs.Round < vote.Round,
// the peer is sending us CatchupCommit precommits.
// We could make note of this and help filter in broadcastHasVoteMessage().
2015-08-12 11:00:23 -07:00
conR.broadcastHasVoteMessage(vote, valIndex)
2014-08-10 16:35:08 -07:00
}
default:
2015-08-26 15:56:34 -07:00
// don't punish (leave room for soft upgrades)
log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg)))
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
default:
log.Warn(Fmt("Unknown channel %X", chID))
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
if err != nil {
2014-12-29 18:39:19 -08:00
log.Warn("Error in Receive()", "error", err)
2014-09-14 15:37:32 -07:00
}
2014-08-10 16:35:08 -07:00
}
2015-06-22 19:04:31 -07:00
// Broadcasts HasVoteMessage to peers that care.
func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote, index int) {
2015-06-22 19:04:31 -07:00
msg := &HasVoteMessage{
Height: vote.Height,
Round: vote.Round,
Type: vote.Type,
Index: index,
}
conR.Switch.Broadcast(StateChannel, msg)
2015-06-22 19:04:31 -07:00
/*
// TODO: Make this broadcast more selective.
for _, peer := range conR.Switch.Peers().List() {
2015-06-22 19:04:31 -07:00
ps := peer.Data.Get(PeerStateKey).(*PeerState)
prs := ps.GetRoundState()
if prs.Height == vote.Height {
// TODO: Also filter on round?
peer.TrySend(StateChannel, msg)
} else {
// Height doesn't match
// TODO: check a field, maybe CatchupCommitRound?
// TODO: But that requires changing the struct field comment.
}
}
*/
}
2014-10-22 17:20:44 -07:00
// Sets our private validator account for signing votes.
func (conR *ConsensusReactor) SetPrivValidator(priv *types.PrivValidator) {
2014-10-22 17:20:44 -07:00
conR.conS.SetPrivValidator(priv)
}
// implements events.Eventable
2015-04-15 23:40:27 -07:00
func (conR *ConsensusReactor) SetFireable(evsw events.Fireable) {
conR.evsw = evsw
2015-04-15 23:40:27 -07:00
conR.conS.SetFireable(evsw)
}
2014-10-21 23:30:18 -07:00
//--------------------------------------
func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) {
nrsMsg = &NewRoundStepMessage{
Height: rs.Height,
Round: rs.Round,
Step: rs.Step,
SecondsSinceStartTime: int(time.Now().Sub(rs.StartTime).Seconds()),
2015-06-19 15:30:10 -07:00
LastCommitRound: rs.LastCommit.Round(),
}
if rs.Step == RoundStepCommit {
csMsg = &CommitStepMessage{
2015-06-22 19:04:31 -07:00
Height: rs.Height,
BlockPartsHeader: rs.ProposalBlockParts.Header(),
BlockParts: rs.ProposalBlockParts.BitArray(),
}
}
return
}
2014-10-30 03:32:09 -07:00
// Listens for changes to the ConsensusState.Step by pulling
// on conR.conS.NewStepCh().
func (conR *ConsensusReactor) broadcastNewRoundStepRoutine() {
for {
2014-10-30 03:32:09 -07:00
// Get RoundState with new Step or quit.
var rs *RoundState
select {
case rs = <-conR.conS.NewStepCh():
case <-conR.Quit:
2014-10-30 03:32:09 -07:00
return
}
nrsMsg, csMsg := makeRoundStepMessages(rs)
if nrsMsg != nil {
conR.Switch.Broadcast(StateChannel, nrsMsg)
}
if csMsg != nil {
conR.Switch.Broadcast(StateChannel, csMsg)
}
}
2014-10-21 23:30:18 -07:00
}
2015-06-19 15:30:10 -07:00
func (conR *ConsensusReactor) sendNewRoundStepMessage(peer *p2p.Peer) {
rs := conR.conS.GetRoundState()
nrsMsg, csMsg := makeRoundStepMessages(rs)
if nrsMsg != nil {
peer.Send(StateChannel, nrsMsg)
}
if csMsg != nil {
2015-04-20 18:51:20 -07:00
peer.Send(StateChannel, csMsg)
}
}
2014-09-14 15:37:32 -07:00
func (conR *ConsensusReactor) gossipDataRoutine(peer *p2p.Peer, ps *PeerState) {
2015-12-09 11:54:08 -08:00
log := log.New("peer", peer)
2014-09-14 15:37:32 -07:00
2014-08-10 16:35:08 -07:00
OUTER_LOOP:
for {
2014-09-14 15:37:32 -07:00
// Manage disconnects from self or peer.
if !peer.IsRunning() || !conR.IsRunning() {
2015-07-19 14:49:13 -07:00
log.Notice(Fmt("Stopping gossipDataRoutine for %v.", peer))
2014-09-14 15:37:32 -07:00
return
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
rs := conR.conS.GetRoundState()
prs := ps.GetRoundState()
2014-08-10 16:35:08 -07:00
2014-10-26 13:26:27 -07:00
// Send proposal Block parts?
2015-06-22 19:04:31 -07:00
if rs.ProposalBlockParts.HasHeader(prs.ProposalBlockPartsHeader) {
2015-07-19 14:49:13 -07:00
//log.Info("ProposalBlockParts matched", "blockParts", prs.ProposalBlockParts)
2015-06-22 19:04:31 -07:00
if index, ok := rs.ProposalBlockParts.BitArray().Sub(prs.ProposalBlockParts.Copy()).PickRandom(); ok {
part := rs.ProposalBlockParts.GetPart(index)
2015-06-22 19:04:31 -07:00
msg := &BlockPartMessage{
2015-06-25 14:05:18 -07:00
Height: rs.Height, // This tells peer that this part applies to us.
Round: rs.Round, // This tells peer that this part applies to us.
Part: part,
}
peer.Send(DataChannel, msg)
2015-06-25 14:05:18 -07:00
ps.SetHasProposalBlockPart(prs.Height, prs.Round, index)
continue OUTER_LOOP
}
}
// If the peer is on a previous height, help catch up.
2015-06-25 12:52:16 -07:00
if (0 < prs.Height) && (prs.Height < rs.Height) {
2015-07-19 14:49:13 -07:00
//log.Info("Data catchup", "height", rs.Height, "peerHeight", prs.Height, "peerProposalBlockParts", prs.ProposalBlockParts)
2015-06-22 19:04:31 -07:00
if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok {
// Ensure that the peer's PartSetHeader is correct
blockMeta := conR.blockStore.LoadBlockMeta(prs.Height)
2015-06-22 19:04:31 -07:00
if !blockMeta.PartsHeader.Equals(prs.ProposalBlockPartsHeader) {
2015-07-19 14:49:13 -07:00
log.Info("Peer ProposalBlockPartsHeader mismatch, sleeping",
2015-06-22 19:04:31 -07:00
"peerHeight", prs.Height, "blockPartsHeader", blockMeta.PartsHeader, "peerBlockPartsHeader", prs.ProposalBlockPartsHeader)
time.Sleep(peerGossipSleepDuration)
continue OUTER_LOOP
}
// Load the part
part := conR.blockStore.LoadBlockPart(prs.Height, index)
if part == nil {
log.Warn("Could not load part", "index", index,
2015-06-22 19:04:31 -07:00
"peerHeight", prs.Height, "blockPartsHeader", blockMeta.PartsHeader, "peerBlockPartsHeader", prs.ProposalBlockPartsHeader)
time.Sleep(peerGossipSleepDuration)
continue OUTER_LOOP
}
// Send the part
2015-06-22 19:04:31 -07:00
msg := &BlockPartMessage{
2015-06-25 14:05:18 -07:00
Height: prs.Height, // Not our height, so it doesn't matter.
Round: prs.Round, // Not our height, so it doesn't matter.
Part: part,
}
peer.Send(DataChannel, msg)
ps.SetHasProposalBlockPart(prs.Height, prs.Round, index)
continue OUTER_LOOP
} else {
2015-07-19 14:49:13 -07:00
//log.Info("No parts to send in catch-up, sleeping")
time.Sleep(peerGossipSleepDuration)
continue OUTER_LOOP
}
}
// If height and round don't match, sleep.
2015-06-25 14:05:18 -07:00
if (rs.Height != prs.Height) || (rs.Round != prs.Round) {
2015-07-19 14:49:13 -07:00
//log.Info("Peer Height|Round mismatch, sleeping", "peerHeight", prs.Height, "peerRound", prs.Round, "peer", peer)
2014-09-14 15:37:32 -07:00
time.Sleep(peerGossipSleepDuration)
2014-08-10 16:35:08 -07:00
continue OUTER_LOOP
}
2015-06-22 19:04:31 -07:00
// By here, height and round match.
2015-06-25 14:05:18 -07:00
// Proposal block parts were already matched and sent if any were wanted.
// (These can match on hash so the round doesn't matter)
// Now consider sending other things, like the Proposal itself.
2015-06-22 19:04:31 -07:00
// Send Proposal && ProposalPOL BitArray?
2014-09-14 15:37:32 -07:00
if rs.Proposal != nil && !prs.Proposal {
2015-06-22 19:04:31 -07:00
// Proposal
{
msg := &ProposalMessage{Proposal: rs.Proposal}
peer.Send(DataChannel, msg)
ps.SetHasProposal(rs.Proposal)
}
// ProposalPOL.
2015-06-25 14:05:18 -07:00
// Peer must receive ProposalMessage first.
// rs.Proposal was validated, so rs.Proposal.POLRound <= rs.Round,
// so we definitely have rs.Votes.Prevotes(rs.Proposal.POLRound).
2015-06-22 19:04:31 -07:00
if 0 <= rs.Proposal.POLRound {
msg := &ProposalPOLMessage{
Height: rs.Height,
ProposalPOLRound: rs.Proposal.POLRound,
ProposalPOL: rs.Votes.Prevotes(rs.Proposal.POLRound).BitArray(),
2015-06-22 19:04:31 -07:00
}
peer.Send(DataChannel, msg)
}
2014-09-14 15:37:32 -07:00
continue OUTER_LOOP
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
// Nothing to do. Sleep.
time.Sleep(peerGossipSleepDuration)
continue OUTER_LOOP
2014-08-10 16:35:08 -07:00
}
}
2014-09-14 15:37:32 -07:00
func (conR *ConsensusReactor) gossipVotesRoutine(peer *p2p.Peer, ps *PeerState) {
2015-12-09 11:54:08 -08:00
log := log.New("peer", peer)
2015-05-04 22:21:07 -07:00
// Simple hack to throttle logs upon sleep.
var sleeping = 0
2014-09-14 15:37:32 -07:00
OUTER_LOOP:
for {
// Manage disconnects from self or peer.
if !peer.IsRunning() || !conR.IsRunning() {
2015-07-19 14:49:13 -07:00
log.Notice(Fmt("Stopping gossipVotesRoutine for %v.", peer))
2014-09-14 15:37:32 -07:00
return
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
rs := conR.conS.GetRoundState()
prs := ps.GetRoundState()
2015-05-04 22:21:07 -07:00
switch sleeping {
case 1: // First sleep
sleeping = 2
case 2: // No more sleep
sleeping = 0
}
2015-12-09 11:54:08 -08:00
//log.Debug("gossipVotesRoutine", "rsHeight", rs.Height, "rsRound", rs.Round,
// "prsHeight", prs.Height, "prsRound", prs.Round, "prsStep", prs.Step)
2015-06-19 15:30:10 -07:00
// If height matches, then send LastCommit, Prevotes, Precommits.
if rs.Height == prs.Height {
2015-06-19 15:30:10 -07:00
// If there are lastCommits to send...
if prs.Step == RoundStepNewHeight {
if ps.PickSendVote(rs.LastCommit) {
2015-07-19 14:49:13 -07:00
log.Info("Picked rs.LastCommit to send")
2015-05-07 17:35:58 -07:00
continue OUTER_LOOP
}
}
// If there are prevotes to send...
2015-10-12 15:19:55 -07:00
if prs.Step <= RoundStepPrevote && prs.Round != -1 && prs.Round <= rs.Round {
if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) {
2015-07-19 14:49:13 -07:00
log.Info("Picked rs.Prevotes(prs.Round) to send")
2015-06-26 17:07:19 -07:00
continue OUTER_LOOP
}
}
2015-10-12 15:19:55 -07:00
// If there are precommits to send...
if prs.Step <= RoundStepPrecommit && prs.Round != -1 && prs.Round <= rs.Round {
if ps.PickSendVote(rs.Votes.Precommits(prs.Round)) {
2015-07-19 14:49:13 -07:00
log.Info("Picked rs.Precommits(prs.Round) to send")
2015-06-26 17:07:19 -07:00
continue OUTER_LOOP
}
}
2015-06-22 19:04:31 -07:00
// If there are POLPrevotes to send...
2015-10-12 15:19:55 -07:00
if prs.ProposalPOLRound != -1 {
if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil {
if ps.PickSendVote(polPrevotes) {
2015-07-19 14:49:13 -07:00
log.Info("Picked rs.Prevotes(prs.ProposalPOLRound) to send")
2015-06-22 19:04:31 -07:00
continue OUTER_LOOP
}
}
}
2014-09-14 15:37:32 -07:00
}
2015-06-19 15:30:10 -07:00
// Special catchup logic.
// If peer is lagging by height 1, send LastCommit.
if prs.Height != 0 && rs.Height == prs.Height+1 {
if ps.PickSendVote(rs.LastCommit) {
2015-07-19 14:49:13 -07:00
log.Info("Picked rs.LastCommit to send")
continue OUTER_LOOP
}
2014-09-14 15:37:32 -07:00
}
2014-08-10 16:35:08 -07:00
2015-06-19 15:30:10 -07:00
// Catchup logic
// If peer is lagging by more than 1, send Validation.
if prs.Height != 0 && rs.Height >= prs.Height+2 {
2015-06-19 15:30:10 -07:00
// Load the block validation for prs.Height,
// which contains precommit signatures for prs.Height.
validation := conR.blockStore.LoadBlockValidation(prs.Height)
2015-07-19 14:49:13 -07:00
log.Info("Loaded BlockValidation for catch-up", "height", prs.Height, "validation", validation)
if ps.PickSendVote(validation) {
2015-07-19 14:49:13 -07:00
log.Info("Picked Catchup validation to send")
2015-06-19 15:30:10 -07:00
continue OUTER_LOOP
}
}
2015-05-04 22:21:07 -07:00
if sleeping == 0 {
// We sent nothing. Sleep...
sleeping = 1
2015-07-19 14:49:13 -07:00
log.Info("No votes to send, sleeping", "peer", peer,
2015-06-22 19:04:31 -07:00
"localPV", rs.Votes.Prevotes(rs.Round).BitArray(), "peerPV", prs.Prevotes,
"localPC", rs.Votes.Precommits(rs.Round).BitArray(), "peerPC", prs.Precommits)
2015-05-04 22:21:07 -07:00
} else if sleeping == 2 {
// Continued sleep...
sleeping = 1
}
2014-09-14 15:37:32 -07:00
time.Sleep(peerGossipSleepDuration)
continue OUTER_LOOP
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
}
2014-08-10 16:35:08 -07:00
//-----------------------------------------------------------------------------
2014-09-14 15:37:32 -07:00
// Read only when returned by PeerState.GetRoundState().
type PeerRoundState struct {
Height int // Height peer is at
2015-09-10 01:29:49 -07:00
Round int // Round peer is at, -1 if unknown.
2015-06-22 19:04:31 -07:00
Step RoundStepType // Step peer is at
StartTime time.Time // Estimated start of round 0 at this height
Proposal bool // True if peer has proposal for this round
ProposalBlockPartsHeader types.PartSetHeader //
ProposalBlockParts *BitArray //
2015-09-10 01:29:49 -07:00
ProposalPOLRound int // Proposal's POL round. -1 if none.
2015-06-22 19:04:31 -07:00
ProposalPOL *BitArray // nil until ProposalPOLMessage received.
Prevotes *BitArray // All votes peer has for this round
Precommits *BitArray // All precommits peer has for this round
2015-09-10 01:29:49 -07:00
LastCommitRound int // Round of commit for last height. -1 if none.
2015-06-22 19:04:31 -07:00
LastCommit *BitArray // All commit precommits of commit for last height.
2015-10-12 15:19:55 -07:00
CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none.
CatchupCommit *BitArray // All commit precommits peer has for this height & CatchupCommitRound
2014-09-14 15:37:32 -07:00
}
//-----------------------------------------------------------------------------
2014-08-10 16:35:08 -07:00
var (
ErrPeerStateHeightRegression = errors.New("Error peer state height regression")
ErrPeerStateInvalidStartTime = errors.New("Error peer state invalid startTime")
)
type PeerState struct {
Peer *p2p.Peer
2014-09-14 15:37:32 -07:00
mtx sync.Mutex
PeerRoundState
2014-08-10 16:35:08 -07:00
}
func NewPeerState(peer *p2p.Peer) *PeerState {
2015-09-10 01:29:49 -07:00
return &PeerState{
Peer: peer,
PeerRoundState: PeerRoundState{
Round: -1,
ProposalPOLRound: -1,
LastCommitRound: -1,
CatchupCommitRound: -1,
},
}
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
// Returns an atomic snapshot of the PeerRoundState.
// There's no point in mutating it since it won't change PeerState.
func (ps *PeerState) GetRoundState() *PeerRoundState {
ps.mtx.Lock()
defer ps.mtx.Unlock()
2014-09-14 15:37:32 -07:00
prs := ps.PeerRoundState // copy
return &prs
}
2015-09-25 09:55:59 -07:00
// Returns an atomic snapshot of the PeerRoundState's height
// used by the mempool to ensure peers are caught up before broadcasting new txs
func (ps *PeerState) GetHeight() int {
ps.mtx.Lock()
defer ps.mtx.Unlock()
return ps.PeerRoundState.Height
}
func (ps *PeerState) SetHasProposal(proposal *types.Proposal) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
if ps.Height != proposal.Height || ps.Round != proposal.Round {
return
}
if ps.Proposal {
return
}
ps.Proposal = true
2015-06-22 19:04:31 -07:00
ps.ProposalBlockPartsHeader = proposal.BlockPartsHeader
ps.ProposalBlockParts = NewBitArray(proposal.BlockPartsHeader.Total)
2015-06-22 19:04:31 -07:00
ps.ProposalPOLRound = proposal.POLRound
ps.ProposalPOL = nil // Nil until ProposalPOLMessage received.
2014-09-14 15:37:32 -07:00
}
func (ps *PeerState) SetHasProposalBlockPart(height int, round int, index int) {
2014-08-10 16:35:08 -07:00
ps.mtx.Lock()
defer ps.mtx.Unlock()
2014-09-14 15:37:32 -07:00
if ps.Height != height || ps.Round != round {
return
2014-08-10 16:35:08 -07:00
}
ps.ProposalBlockParts.SetIndex(index, true)
}
// Convenience function to send vote to peer.
// Returns true if vote was sent.
func (ps *PeerState) PickSendVote(votes types.VoteSetReader) (ok bool) {
if index, vote, ok := ps.PickVoteToSend(votes); ok {
msg := &VoteMessage{index, vote}
ps.Peer.Send(VoteChannel, msg)
return true
}
return false
}
// votes: Must be the correct Size() for the Height().
func (ps *PeerState) PickVoteToSend(votes types.VoteSetReader) (index int, vote *types.Vote, ok bool) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
if votes.Size() == 0 {
return 0, nil, false
}
height, round, type_, size := votes.Height(), votes.Round(), votes.Type(), votes.Size()
// Lazily set data using 'votes'.
if votes.IsCommit() {
ps.ensureCatchupCommitRound(height, round, size)
}
ps.ensureVoteBitArrays(height, size)
psVotes := ps.getVoteBitArray(height, round, type_)
if psVotes == nil {
return 0, nil, false // Not something worth sending
}
if index, ok := votes.BitArray().Sub(psVotes).PickRandom(); ok {
ps.setHasVote(height, round, type_, index)
return index, votes.GetByIndex(index), true
}
return 0, nil, false
}
func (ps *PeerState) getVoteBitArray(height, round int, type_ byte) *BitArray {
if ps.Height == height {
if ps.Round == round {
switch type_ {
case types.VoteTypePrevote:
return ps.Prevotes
case types.VoteTypePrecommit:
return ps.Precommits
default:
2015-07-19 16:42:52 -07:00
PanicSanity(Fmt("Unexpected vote type %X", type_))
}
}
if ps.CatchupCommitRound == round {
switch type_ {
case types.VoteTypePrevote:
return nil
case types.VoteTypePrecommit:
return ps.CatchupCommit
default:
2015-07-19 16:42:52 -07:00
PanicSanity(Fmt("Unexpected vote type %X", type_))
}
}
return nil
}
if ps.Height == height+1 {
if ps.LastCommitRound == round {
switch type_ {
case types.VoteTypePrevote:
return nil
case types.VoteTypePrecommit:
return ps.LastCommit
default:
2015-07-19 16:42:52 -07:00
PanicSanity(Fmt("Unexpected vote type %X", type_))
}
}
return nil
}
return nil
}
2015-10-12 15:19:55 -07:00
// 'round': A round for which we have a +2/3 commit.
func (ps *PeerState) ensureCatchupCommitRound(height, round int, numValidators int) {
if ps.Height != height {
return
}
2015-10-12 15:19:55 -07:00
/*
NOTE: This is wrong, 'round' could change.
e.g. if orig round is not the same as block LastValidation round.
if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round {
PanicSanity(Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round))
}
*/
if ps.CatchupCommitRound == round {
return // Nothing to do!
}
ps.CatchupCommitRound = round
if round == ps.Round {
ps.CatchupCommit = ps.Precommits
} else {
ps.CatchupCommit = NewBitArray(numValidators)
}
}
2015-06-25 14:05:18 -07:00
// NOTE: It's important to make sure that numValidators actually matches
// what the node sees as the number of validators for height.
func (ps *PeerState) EnsureVoteBitArrays(height int, numValidators int) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
ps.ensureVoteBitArrays(height, numValidators)
}
func (ps *PeerState) ensureVoteBitArrays(height int, numValidators int) {
2015-05-07 17:35:58 -07:00
if ps.Height == height {
if ps.Prevotes == nil {
ps.Prevotes = NewBitArray(numValidators)
}
if ps.Precommits == nil {
ps.Precommits = NewBitArray(numValidators)
}
2015-06-19 15:30:10 -07:00
if ps.CatchupCommit == nil {
ps.CatchupCommit = NewBitArray(numValidators)
2015-05-07 17:35:58 -07:00
}
2015-06-22 19:04:31 -07:00
if ps.ProposalPOL == nil {
ps.ProposalPOL = NewBitArray(numValidators)
}
2015-05-07 17:35:58 -07:00
} else if ps.Height == height+1 {
2015-06-19 15:30:10 -07:00
if ps.LastCommit == nil {
ps.LastCommit = NewBitArray(numValidators)
2015-05-07 17:35:58 -07:00
}
}
2014-08-10 16:35:08 -07:00
}
func (ps *PeerState) SetHasVote(vote *types.Vote, index int) {
2014-08-10 16:35:08 -07:00
ps.mtx.Lock()
defer ps.mtx.Unlock()
ps.setHasVote(vote.Height, vote.Round, vote.Type, index)
}
func (ps *PeerState) setHasVote(height int, round int, type_ byte, index int) {
2015-12-09 11:54:08 -08:00
log := log.New("peer", ps.Peer, "peerRound", ps.Round, "height", height, "round", round)
if type_ != types.VoteTypePrevote && type_ != types.VoteTypePrecommit {
2015-07-19 16:42:52 -07:00
PanicSanity("Invalid vote type")
}
if ps.Height == height {
if ps.Round == round {
switch type_ {
case types.VoteTypePrevote:
ps.Prevotes.SetIndex(index, true)
2015-07-19 14:49:13 -07:00
log.Info("SetHasVote(round-match)", "prevotes", ps.Prevotes, "index", index)
case types.VoteTypePrecommit:
ps.Precommits.SetIndex(index, true)
2015-07-19 14:49:13 -07:00
log.Info("SetHasVote(round-match)", "precommits", ps.Precommits, "index", index)
}
} else if ps.CatchupCommitRound == round {
switch type_ {
case types.VoteTypePrevote:
case types.VoteTypePrecommit:
ps.CatchupCommit.SetIndex(index, true)
2015-07-19 14:49:13 -07:00
log.Info("SetHasVote(CatchupCommit)", "precommits", ps.Precommits, "index", index)
}
} else if ps.ProposalPOLRound == round {
switch type_ {
case types.VoteTypePrevote:
ps.ProposalPOL.SetIndex(index, true)
2015-07-19 14:49:13 -07:00
log.Info("SetHasVote(ProposalPOL)", "prevotes", ps.Prevotes, "index", index)
case types.VoteTypePrecommit:
}
2015-06-22 19:04:31 -07:00
}
} else if ps.Height == height+1 {
if ps.LastCommitRound == round {
switch type_ {
case types.VoteTypePrevote:
case types.VoteTypePrecommit:
ps.LastCommit.SetIndex(index, true)
2015-07-19 14:49:13 -07:00
log.Info("setHasVote(LastCommit)", "lastCommit", ps.LastCommit, "index", index)
}
2015-06-19 15:30:10 -07:00
}
} else {
// Does not apply.
}
}
2015-08-26 15:56:34 -07:00
func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage) {
2014-08-10 16:35:08 -07:00
ps.mtx.Lock()
defer ps.mtx.Unlock()
2014-09-14 15:37:32 -07:00
2015-07-05 19:05:07 -07:00
// Ignore duplicate messages.
if ps.Height == msg.Height && ps.Round == msg.Round && ps.Step == msg.Step {
return
}
// Just remember these values.
psHeight := ps.Height
psRound := ps.Round
//psStep := ps.Step
2015-06-19 15:30:10 -07:00
psCatchupCommitRound := ps.CatchupCommitRound
2015-06-22 19:04:31 -07:00
psCatchupCommit := ps.CatchupCommit
2014-09-14 15:37:32 -07:00
startTime := time.Now().Add(-1 * time.Duration(msg.SecondsSinceStartTime) * time.Second)
ps.Height = msg.Height
ps.Round = msg.Round
ps.Step = msg.Step
ps.StartTime = startTime
if psHeight != msg.Height || psRound != msg.Round {
2014-10-25 14:27:53 -07:00
ps.Proposal = false
2015-06-22 19:04:31 -07:00
ps.ProposalBlockPartsHeader = types.PartSetHeader{}
ps.ProposalBlockParts = nil
ps.ProposalPOLRound = -1
ps.ProposalPOL = nil
// We'll update the BitArray capacity later.
ps.Prevotes = nil
ps.Precommits = nil
2014-10-25 14:27:53 -07:00
}
if psHeight == msg.Height && psRound != msg.Round && msg.Round == psCatchupCommitRound {
2015-06-19 15:30:10 -07:00
// Peer caught up to CatchupCommitRound.
2015-06-25 14:05:18 -07:00
// Preserve psCatchupCommit!
// NOTE: We prefer to use prs.Precommits if
// pr.Round matches pr.CatchupCommitRound.
2015-06-19 15:30:10 -07:00
ps.Precommits = psCatchupCommit
}
if psHeight != msg.Height {
2015-06-19 15:30:10 -07:00
// Shift Precommits to LastCommit.
if psHeight+1 == msg.Height && psRound == msg.LastCommitRound {
ps.LastCommitRound = msg.LastCommitRound
ps.LastCommit = ps.Precommits
} else {
2015-06-19 15:30:10 -07:00
ps.LastCommitRound = msg.LastCommitRound
ps.LastCommit = nil
}
// We'll update the BitArray capacity later.
2015-06-19 15:30:10 -07:00
ps.CatchupCommitRound = -1
ps.CatchupCommit = nil
2014-09-14 15:37:32 -07:00
}
2014-08-10 16:35:08 -07:00
}
func (ps *PeerState) ApplyCommitStepMessage(msg *CommitStepMessage) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
if ps.Height != msg.Height {
return
}
2015-06-22 19:04:31 -07:00
ps.ProposalBlockPartsHeader = msg.BlockPartsHeader
ps.ProposalBlockParts = msg.BlockParts
}
func (ps *PeerState) ApplyHasVoteMessage(msg *HasVoteMessage) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
2015-06-19 15:30:10 -07:00
if ps.Height != msg.Height {
return
}
ps.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index)
}
2015-06-22 19:04:31 -07:00
func (ps *PeerState) ApplyProposalPOLMessage(msg *ProposalPOLMessage) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
if ps.Height != msg.Height {
return
}
if ps.ProposalPOLRound != msg.ProposalPOLRound {
2015-06-22 19:04:31 -07:00
return
}
// TODO: Merge onto existing ps.ProposalPOL?
// We might have sent some prevotes in the meantime.
ps.ProposalPOL = msg.ProposalPOL
}
2014-08-10 16:35:08 -07:00
//-----------------------------------------------------------------------------
// Messages
const (
2014-09-14 15:37:32 -07:00
msgTypeNewRoundStep = byte(0x01)
msgTypeCommitStep = byte(0x02)
msgTypeProposal = byte(0x11)
2015-06-22 19:04:31 -07:00
msgTypeProposalPOL = byte(0x12)
msgTypeBlockPart = byte(0x13) // both block & POL
msgTypeVote = byte(0x14)
msgTypeHasVote = byte(0x15)
2014-08-10 16:35:08 -07:00
)
2015-04-14 15:57:16 -07:00
type ConsensusMessage interface{}
2015-07-25 15:45:45 -07:00
var _ = wire.RegisterInterface(
2015-04-14 15:57:16 -07:00
struct{ ConsensusMessage }{},
2015-07-25 15:45:45 -07:00
wire.ConcreteType{&NewRoundStepMessage{}, msgTypeNewRoundStep},
wire.ConcreteType{&CommitStepMessage{}, msgTypeCommitStep},
wire.ConcreteType{&ProposalMessage{}, msgTypeProposal},
wire.ConcreteType{&ProposalPOLMessage{}, msgTypeProposalPOL},
wire.ConcreteType{&BlockPartMessage{}, msgTypeBlockPart},
wire.ConcreteType{&VoteMessage{}, msgTypeVote},
wire.ConcreteType{&HasVoteMessage{}, msgTypeHasVote},
2015-04-14 15:57:16 -07:00
)
2014-08-10 16:35:08 -07:00
// TODO: check for unnecessary extra bytes at the end.
2015-04-14 15:57:16 -07:00
func DecodeMessage(bz []byte) (msgType byte, msg ConsensusMessage, err error) {
msgType = bz[0]
2015-11-10 13:10:43 -08:00
n := new(int)
r := bytes.NewReader(bz)
2015-11-10 13:10:43 -08:00
msg = wire.ReadBinary(struct{ ConsensusMessage }{}, r, maxConsensusMessageSize, n, &err).(struct{ ConsensusMessage }).ConsensusMessage
return
2014-08-10 16:35:08 -07:00
}
//-------------------------------------
2015-06-19 15:30:10 -07:00
// For every height/round/step transition
2014-09-14 15:37:32 -07:00
type NewRoundStepMessage struct {
Height int
Round int
Step RoundStepType
SecondsSinceStartTime int
LastCommitRound int
2014-08-10 16:35:08 -07:00
}
2014-09-14 15:37:32 -07:00
func (m *NewRoundStepMessage) String() string {
2015-06-19 15:30:10 -07:00
return fmt.Sprintf("[NewRoundStep H:%v R:%v S:%v LCR:%v]",
m.Height, m.Round, m.Step, m.LastCommitRound)
2014-08-10 16:35:08 -07:00
}
//-------------------------------------
type CommitStepMessage struct {
Height int
2015-06-22 19:04:31 -07:00
BlockPartsHeader types.PartSetHeader
BlockParts *BitArray
}
func (m *CommitStepMessage) String() string {
2015-06-22 19:04:31 -07:00
return fmt.Sprintf("[CommitStep H:%v BP:%v BA:%v]", m.Height, m.BlockPartsHeader, m.BlockParts)
}
//-------------------------------------
type ProposalMessage struct {
Proposal *types.Proposal
}
func (m *ProposalMessage) String() string {
return fmt.Sprintf("[Proposal %v]", m.Proposal)
}
//-------------------------------------
2015-06-22 19:04:31 -07:00
type ProposalPOLMessage struct {
Height int
ProposalPOLRound int
2015-06-22 19:04:31 -07:00
ProposalPOL *BitArray
}
func (m *ProposalPOLMessage) String() string {
return fmt.Sprintf("[ProposalPOL H:%v POLR:%v POL:%v]", m.Height, m.ProposalPOLRound, m.ProposalPOL)
}
2014-09-14 15:37:32 -07:00
2015-06-22 19:04:31 -07:00
//-------------------------------------
type BlockPartMessage struct {
Height int
Round int
Part *types.Part
2014-08-10 16:35:08 -07:00
}
2015-06-22 19:04:31 -07:00
func (m *BlockPartMessage) String() string {
2015-06-26 17:14:40 -07:00
return fmt.Sprintf("[BlockPart H:%v R:%v P:%v]", m.Height, m.Round, m.Part)
2014-08-10 16:35:08 -07:00
}
//-------------------------------------
type VoteMessage struct {
ValidatorIndex int
Vote *types.Vote
2014-08-10 16:35:08 -07:00
}
func (m *VoteMessage) String() string {
2015-05-06 00:47:20 -07:00
return fmt.Sprintf("[Vote VI:%v V:%v VI:%v]", m.ValidatorIndex, m.Vote, m.ValidatorIndex)
2014-08-10 16:35:08 -07:00
}
//-------------------------------------
type HasVoteMessage struct {
Height int
Round int
Type byte
Index int
}
func (m *HasVoteMessage) String() string {
2015-05-06 00:47:20 -07:00
return fmt.Sprintf("[HasVote VI:%v V:{%v/%02d/%v} VI:%v]", m.Index, m.Height, m.Round, m.Type, m.Index)
}