|
|
- package consensus
-
- import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "math"
- "sync"
- "sync/atomic"
- "time"
-
- . "github.com/tendermint/tendermint/binary"
- . "github.com/tendermint/tendermint/blocks"
- . "github.com/tendermint/tendermint/common"
- . "github.com/tendermint/tendermint/mempool"
- "github.com/tendermint/tendermint/p2p"
- . "github.com/tendermint/tendermint/state"
- )
-
- const (
- StateCh = byte(0x20)
- DataCh = byte(0x21)
- VoteCh = byte(0x22)
-
- peerStateKey = "ConsensusReactor.peerState"
-
- voteTypeNil = byte(0x00)
- voteTypeBlock = byte(0x01)
-
- roundDuration0 = 60 * time.Second // The first round is 60 seconds long.
- roundDurationDelta = 15 * time.Second // Each successive round lasts 15 seconds longer.
- roundDeadlineBare = float64(1.0 / 3.0) // When the bare vote is due.
- roundDeadlinePrecommit = float64(2.0 / 3.0) // When the precommit vote is due.
-
- newBlockWaitDuration = roundDuration0 / 3 // The time to wait between commitTime and startTime of next consensus rounds.
- peerGossipSleepDuration = 50 * time.Millisecond // Time to sleep if there's nothing to send.
- hasVotesThreshold = 50 // After this many new votes we'll send a HasVotesMessage.
- )
-
- //-----------------------------------------------------------------------------
-
- // total duration of given round
- func calcRoundDuration(round uint16) time.Duration {
- return roundDuration0 + roundDurationDelta*time.Duration(round)
- }
-
- // startTime is when round zero started.
- func calcRoundStartTime(round uint16, startTime time.Time) time.Time {
- return startTime.Add(roundDuration0*time.Duration(round) +
- roundDurationDelta*(time.Duration((int64(round)*int64(round)-int64(round))/2)))
- }
-
- // calculates the current round given startTime of round zero.
- // NOTE: round is zero if startTime is in the future.
- func calcRound(startTime time.Time) uint16 {
- now := time.Now()
- if now.Before(startTime) {
- return 0
- }
- // Start + D_0 * R + D_delta * (R^2 - R)/2 <= Now; find largest integer R.
- // D_delta * R^2 + (2D_0 - D_delta) * R + 2(Start - Now) <= 0.
- // AR^2 + BR + C <= 0; A = D_delta, B = (2_D0 - D_delta), C = 2(Start - Now).
- // R = Floor((-B + Sqrt(B^2 - 4AC))/2A)
- A := float64(roundDurationDelta)
- B := 2.0*float64(roundDuration0) - float64(roundDurationDelta)
- C := 2.0 * float64(startTime.Sub(now))
- R := math.Floor((-B + math.Sqrt(B*B-4.0*A*C)/(2*A)))
- if math.IsNaN(R) {
- panic("Could not calc round, should not happen")
- }
- if R > math.MaxInt16 {
- Panicf("Could not calc round, round overflow: %v", R)
- }
- if R < 0 {
- return 0
- }
- return uint16(R)
- }
-
- // convenience
- // NOTE: elapsedRatio can be negative if startTime is in the future.
- func calcRoundInfo(startTime time.Time) (round uint16, roundStartTime time.Time, roundDuration time.Duration,
- roundElapsed time.Duration, elapsedRatio float64) {
- round = calcRound(startTime)
- roundStartTime = calcRoundStartTime(round, startTime)
- roundDuration = calcRoundDuration(round)
- roundElapsed = time.Now().Sub(roundStartTime)
- elapsedRatio = float64(roundElapsed) / float64(roundDuration)
- return
- }
-
- //-----------------------------------------------------------------------------
-
- type ConsensusReactor struct {
- sw *p2p.Switch
- quit chan struct{}
- started uint32
- stopped uint32
-
- conS *ConsensusState
- doActionCh chan RoundAction
- }
-
- func NewConsensusReactor(sw *p2p.Switch, blockStore *BlockStore, mempool *Mempool, state *State) *ConsensusReactor {
- conS := NewConsensusState(state, blockStore, mempool)
- conR := &ConsensusReactor{
- sw: sw,
- quit: make(chan struct{}),
-
- conS: conS,
- doActionCh: make(chan RoundAction, 1),
- }
- return conR
- }
-
- // Sets our private validator account for signing votes.
- func (conR *ConsensusReactor) SetPrivValidator(priv *PrivValidator) {
- conR.conS.SetPrivValidator(priv)
- }
-
- func (conR *ConsensusReactor) Start() {
- if atomic.CompareAndSwapUint32(&conR.started, 0, 1) {
- log.Info("Starting ConsensusReactor")
- go conR.proposeAndVoteRoutine()
- }
- }
-
- func (conR *ConsensusReactor) Stop() {
- if atomic.CompareAndSwapUint32(&conR.stopped, 0, 1) {
- log.Info("Stopping ConsensusReactor")
- close(conR.quit)
- }
- }
-
- func (conR *ConsensusReactor) IsStopped() bool {
- return atomic.LoadUint32(&conR.stopped) == 1
- }
-
- // Implements Reactor
- func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor {
- // TODO optimize
- return []*p2p.ChannelDescriptor{
- &p2p.ChannelDescriptor{
- Id: StateCh,
- Priority: 5,
- },
- &p2p.ChannelDescriptor{
- Id: DataCh,
- Priority: 5,
- },
- &p2p.ChannelDescriptor{
- Id: VoteCh,
- Priority: 5,
- },
- }
- }
-
- // Implements Reactor
- func (conR *ConsensusReactor) AddPeer(peer *p2p.Peer) {
- // Create peerState for peer
- peerState := NewPeerState(peer)
- peer.Data.Set(peerStateKey, peerState)
-
- // Begin gossip routines for this peer.
- go conR.gossipDataRoutine(peer, peerState)
- go conR.gossipVotesRoutine(peer, peerState)
- }
-
- // Implements Reactor
- func (conR *ConsensusReactor) RemovePeer(peer *p2p.Peer, reason interface{}) {
- //peer.Data.Get(peerStateKey).(*PeerState).Disconnect()
- }
-
- // Implements Reactor
- func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte) {
-
- // Get round state
- rs := conR.conS.GetRoundState()
- ps := peer.Data.Get(peerStateKey).(*PeerState)
- _, msg_ := decodeMessage(msgBytes)
- voteAddCounter := 0
- var err error = nil
-
- switch chId {
- case StateCh:
- switch msg_.(type) {
- case *NewRoundStepMessage:
- msg := msg_.(*NewRoundStepMessage)
- err = ps.ApplyNewRoundStepMessage(msg)
-
- case *HasVotesMessage:
- msg := msg_.(*HasVotesMessage)
- err = ps.ApplyHasVotesMessage(msg)
-
- default:
- // Ignore unknown message
- }
-
- case DataCh:
- switch msg_.(type) {
- case *Proposal:
- proposal := msg_.(*Proposal)
- ps.SetHasProposal(proposal.Height, proposal.Round)
- err = conR.conS.SetProposal(proposal)
-
- case *PartMessage:
- msg := msg_.(*PartMessage)
- if msg.Type == partTypeProposalBlock {
- ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Index)
- _, err = conR.conS.AddProposalBlockPart(msg.Height, msg.Round, msg.Part)
- } else if msg.Type == partTypeProposalPOL {
- ps.SetHasProposalPOLPart(msg.Height, msg.Round, msg.Part.Index)
- _, err = conR.conS.AddProposalPOLPart(msg.Height, msg.Round, msg.Part)
- } else {
- // Ignore unknown part type
- }
-
- default:
- // Ignore unknown message
- }
-
- case VoteCh:
- switch msg_.(type) {
- case *Vote:
- vote := msg_.(*Vote)
- // We can't deal with votes from another height,
- // as they have a different validator set.
- if vote.Height != rs.Height || vote.Height != ps.Height {
- return
- }
- index, val := rs.Validators.GetById(vote.SignerId)
- if val == nil {
- log.Warning("Peer gave us an invalid vote.")
- return
- }
- ps.SetHasVote(rs.Height, rs.Round, vote.Type, uint32(index))
- added, err := conR.conS.AddVote(vote)
- if err != nil {
- log.Warning("Error attempting to add vote: %v", err)
- }
- if added {
- // Maybe send HasVotesMessage
- voteAddCounter++
- if voteAddCounter%hasVotesThreshold == 0 {
- // TODO optimize.
- msg := &HasVotesMessage{
- Height: rs.Height,
- Round: rs.Round,
- Votes: rs.Votes.BitArray(),
- Precommits: rs.Precommits.BitArray(),
- Commits: rs.Commits.BitArray(),
- }
- conR.sw.Broadcast(StateCh, msg)
- }
- }
-
- default:
- // Ignore unknown message
- }
- default:
- // Ignore unknown channel
- }
-
- if err != nil {
- log.Warning("Error in Receive(): %v", err)
- }
- }
-
- func (conR *ConsensusReactor) gossipDataRoutine(peer *p2p.Peer, ps *PeerState) {
-
- OUTER_LOOP:
- for {
- // Manage disconnects from self or peer.
- if peer.IsStopped() || conR.IsStopped() {
- log.Info("Stopping gossipDataRoutine for %v.", peer)
- return
- }
- rs := conR.conS.GetRoundState()
- prs := ps.GetRoundState()
-
- // If height and round doesn't match, sleep.
- if rs.Height != prs.Height || rs.Round != prs.Round {
- time.Sleep(peerGossipSleepDuration)
- continue OUTER_LOOP
- }
-
- // Send proposal?
- if rs.Proposal != nil && !prs.Proposal {
- msg := p2p.TypedMessage{msgTypeProposal, rs.Proposal}
- peer.Send(DataCh, msg)
- ps.SetHasProposal(rs.Height, rs.Round)
- continue OUTER_LOOP
- }
-
- // Send proposal block part?
- if index, ok := rs.ProposalBlockPartSet.BitArray().Sub(
- prs.ProposalBlockBitArray).PickRandom(); ok {
- msg := &PartMessage{
- Height: rs.Height,
- Round: rs.Round,
- Type: partTypeProposalBlock,
- Part: rs.ProposalBlockPartSet.GetPart(uint16(index)),
- }
- peer.Send(DataCh, msg)
- ps.SetHasProposalBlockPart(rs.Height, rs.Round, uint16(index))
- continue OUTER_LOOP
- }
-
- // Send proposal POL part?
- if index, ok := rs.ProposalPOLPartSet.BitArray().Sub(
- prs.ProposalPOLBitArray).PickRandom(); ok {
- msg := &PartMessage{
- Height: rs.Height,
- Round: rs.Round,
- Type: partTypeProposalPOL,
- Part: rs.ProposalPOLPartSet.GetPart(uint16(index)),
- }
- peer.Send(DataCh, msg)
- ps.SetHasProposalPOLPart(rs.Height, rs.Round, uint16(index))
- continue OUTER_LOOP
- }
-
- // Nothing to do. Sleep.
- time.Sleep(peerGossipSleepDuration)
- continue OUTER_LOOP
- }
- }
-
- func (conR *ConsensusReactor) gossipVotesRoutine(peer *p2p.Peer, ps *PeerState) {
- OUTER_LOOP:
- for {
- // Manage disconnects from self or peer.
- if peer.IsStopped() || conR.IsStopped() {
- log.Info("Stopping gossipVotesRoutine for %v.", peer)
- return
- }
- rs := conR.conS.GetRoundState()
- prs := ps.GetRoundState()
-
- // If height doens't match, sleep.
- if rs.Height != prs.Height {
- time.Sleep(peerGossipSleepDuration)
- continue OUTER_LOOP
- }
-
- // If there are bare votes to send...
- if prs.Step <= RoundStepVote {
- index, ok := rs.Votes.BitArray().Sub(prs.Votes).PickRandom()
- if ok {
- valId, val := rs.Validators.GetByIndex(uint32(index))
- if val != nil {
- vote := rs.Votes.GetVote(valId)
- msg := p2p.TypedMessage{msgTypeVote, vote}
- peer.Send(VoteCh, msg)
- ps.SetHasVote(rs.Height, rs.Round, VoteTypeBare, uint32(index))
- continue OUTER_LOOP
- } else {
- log.Error("index is not a valid validator index")
- }
- }
- }
-
- // If there are precommits to send...
- if prs.Step <= RoundStepPrecommit {
- index, ok := rs.Precommits.BitArray().Sub(prs.Precommits).PickRandom()
- if ok {
- valId, val := rs.Validators.GetByIndex(uint32(index))
- if val != nil {
- vote := rs.Precommits.GetVote(valId)
- msg := p2p.TypedMessage{msgTypeVote, vote}
- peer.Send(VoteCh, msg)
- ps.SetHasVote(rs.Height, rs.Round, VoteTypePrecommit, uint32(index))
- continue OUTER_LOOP
- } else {
- log.Error("index is not a valid validator index")
- }
- }
- }
-
- // If there are any commits to send...
- index, ok := rs.Commits.BitArray().Sub(prs.Commits).PickRandom()
- if ok {
- valId, val := rs.Validators.GetByIndex(uint32(index))
- if val != nil {
- vote := rs.Commits.GetVote(valId)
- msg := p2p.TypedMessage{msgTypeVote, vote}
- peer.Send(VoteCh, msg)
- ps.SetHasVote(rs.Height, rs.Round, VoteTypeCommit, uint32(index))
- continue OUTER_LOOP
- } else {
- log.Error("index is not a valid validator index")
- }
- }
-
- // We sent nothing. Sleep...
- time.Sleep(peerGossipSleepDuration)
- continue OUTER_LOOP
- }
- }
-
- // Signs a vote document and broadcasts it.
- func (conR *ConsensusReactor) signAndBroadcastVote(rs *RoundState, vote *Vote) {
- if rs.PrivValidator != nil {
- rs.PrivValidator.Sign(vote)
- conR.conS.AddVote(vote)
- msg := p2p.TypedMessage{msgTypeVote, vote}
- conR.sw.Broadcast(VoteCh, msg)
- }
- }
-
- //-------------------------------------
-
- func (conR *ConsensusReactor) runStepPropose(rs *RoundState) {
- conR.conS.MakeProposal()
- }
-
- func (conR *ConsensusReactor) runStepVote(rs *RoundState) {
-
- // If we have a locked block, we must vote for that.
- // NOTE: a locked block is already valid.
- if rs.LockedBlock != nil {
- conR.signAndBroadcastVote(rs, &Vote{
- Height: rs.Height,
- Round: rs.Round,
- Type: VoteTypeBare,
- BlockHash: rs.LockedBlock.Hash(),
- })
- }
-
- // Try staging proposed block.
- // If Block is nil, an error is returned.
- err := conR.conS.stageBlock(rs.ProposalBlock)
- if err != nil {
-
- // Vote nil
- conR.signAndBroadcastVote(rs, &Vote{
- Height: rs.Height,
- Round: rs.Round,
- Type: VoteTypeBare,
- BlockHash: nil,
- })
-
- } else {
-
- // Vote for block
- conR.signAndBroadcastVote(rs, &Vote{
- Height: rs.Height,
- Round: rs.Round,
- Type: VoteTypeBare,
- BlockHash: rs.ProposalBlock.Hash(),
- })
- }
- }
-
- func (conR *ConsensusReactor) runStepPrecommit(rs *RoundState) {
-
- // If we see a 2/3 majority of votes for a block, lock.
- hash := conR.conS.LockOrUnlock(rs.Height, rs.Round)
- if len(hash) > 0 {
-
- // Precommit
- conR.signAndBroadcastVote(rs, &Vote{
- Height: rs.Height,
- Round: rs.Round,
- Type: VoteTypePrecommit,
- BlockHash: hash,
- })
-
- }
- }
-
- func (conR *ConsensusReactor) runStepCommit(rs *RoundState) bool {
-
- // If we see a 2/3 majority of precommits for a block, commit.
- block := conR.conS.Commit(rs.Height, rs.Round)
- if block == nil {
- return false
- } else {
- conR.signAndBroadcastVote(rs, &Vote{
- Height: rs.Height,
- Round: rs.Round,
- Type: VoteTypePrecommit,
- BlockHash: block.Hash(),
- })
- return true
- }
- }
-
- //-------------------------------------
-
- type RoundAction struct {
- Height uint32 // The block height for which consensus is reaching for.
- Round uint16 // The round number at given height.
- XnToStep uint8 // Transition to this step. Action depends on this value.
- RoundElapsed time.Duration // Duration since round start.
- }
-
- // Source of all round state transitions and votes.
- // It can be preemptively woken up via a message to
- // doActionCh.
- func (conR *ConsensusReactor) proposeAndVoteRoutine() {
-
- // Figure out when to wake up next (in the absence of other events)
- setAlarm := func() {
- if len(conR.doActionCh) > 0 {
- return // Already going to wake up later.
- }
-
- // Figure out which height/round/step we're at,
- // then schedule an action for when it is due.
- rs := conR.conS.GetRoundState()
- _, _, roundDuration, roundElapsed, elapsedRatio := calcRoundInfo(rs.StartTime)
- switch rs.Step {
- case RoundStepStart:
- // It's a new RoundState.
- if elapsedRatio < 0 {
- // startTime is in the future.
- time.Sleep(time.Duration(-1.0*elapsedRatio) * roundDuration)
- }
- conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundStepPropose, roundElapsed}
- case RoundStepPropose:
- // Wake up when it's time to vote.
- time.Sleep(time.Duration(roundDeadlineBare-elapsedRatio) * roundDuration)
- conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundStepVote, roundElapsed}
- case RoundStepVote:
- // Wake up when it's time to precommit.
- time.Sleep(time.Duration(roundDeadlinePrecommit-elapsedRatio) * roundDuration)
- conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundStepPrecommit, roundElapsed}
- case RoundStepPrecommit:
- // Wake up when the round is over.
- time.Sleep(time.Duration(1.0-elapsedRatio) * roundDuration)
- conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundStepCommit, roundElapsed}
- case RoundStepCommit:
- // This shouldn't happen.
- // Before setAlarm() got called,
- // logic should have created a new RoundState for the next round.
- panic("Should not happen")
- }
- }
-
- for {
- func() {
- roundAction := <-conR.doActionCh
- // Always set the alarm after any processing below.
- defer setAlarm()
-
- height := roundAction.Height
- round := roundAction.Round
- step := roundAction.XnToStep
- roundElapsed := roundAction.RoundElapsed
- rs := conR.conS.GetRoundState()
-
- if height != rs.Height || round != rs.Round {
- return // Action is not relevant
- }
-
- // Run step
- if step == RoundStepPropose && rs.Step == RoundStepStart {
- conR.runStepPropose(rs)
- } else if step == RoundStepVote && rs.Step <= RoundStepPropose {
- conR.runStepVote(rs)
- } else if step == RoundStepPrecommit && rs.Step <= RoundStepVote {
- conR.runStepPrecommit(rs)
- } else if step == RoundStepCommit && rs.Step <= RoundStepPrecommit {
- didCommit := conR.runStepCommit(rs)
- if didCommit {
- // We already set up ConsensusState for the next height
- // (it happens in the call to conR.runStepCommit).
- } else {
- // Prepare a new RoundState for the next state.
- conR.conS.SetupRound(rs.Round + 1)
- return // setAlarm() takes care of the rest.
- }
- } else {
- return // Action is not relevant.
- }
-
- // Transition to new step.
- conR.conS.SetStep(step)
-
- // Broadcast NewRoundStepMessage.
- msg := &NewRoundStepMessage{
- Height: height,
- Round: round,
- Step: step,
- SecondsSinceStartTime: uint32(roundElapsed.Seconds()),
- }
- conR.sw.Broadcast(StateCh, msg)
- }()
- }
- }
-
- //-----------------------------------------------------------------------------
-
- // Read only when returned by PeerState.GetRoundState().
- type PeerRoundState struct {
- Height uint32 // Height peer is at
- Round uint16 // Round peer is at
- Step uint8 // 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
- ProposalBlockHash []byte // Block parts merkle root
- ProposalBlockBitArray BitArray // Block parts bitarray
- ProposalPOLHash []byte // POL parts merkle root
- ProposalPOLBitArray BitArray // POL parts bitarray
- Votes BitArray // All votes peer has for this round
- Precommits BitArray // All precommits peer has for this round
- Commits BitArray // All commits peer has for this height
- }
-
- //-----------------------------------------------------------------------------
-
- var (
- ErrPeerStateHeightRegression = errors.New("Error peer state height regression")
- ErrPeerStateInvalidStartTime = errors.New("Error peer state invalid startTime")
- )
-
- type PeerState struct {
- mtx sync.Mutex
- PeerRoundState
- }
-
- func NewPeerState(peer *p2p.Peer) *PeerState {
- return &PeerState{}
- }
-
- // 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()
- prs := ps.PeerRoundState // copy
- return &prs
- }
-
- func (ps *PeerState) SetHasProposal(height uint32, round uint16) {
- ps.mtx.Lock()
- defer ps.mtx.Unlock()
-
- if ps.Height == height && ps.Round == round {
- ps.Proposal = true
- }
- }
-
- func (ps *PeerState) SetHasProposalBlockPart(height uint32, round uint16, index uint16) {
- ps.mtx.Lock()
- defer ps.mtx.Unlock()
-
- if ps.Height == height && ps.Round == round {
- ps.ProposalBlockBitArray.SetIndex(uint(index), true)
- }
- }
-
- func (ps *PeerState) SetHasProposalPOLPart(height uint32, round uint16, index uint16) {
- ps.mtx.Lock()
- defer ps.mtx.Unlock()
- if ps.Height == height && ps.Round == round {
- ps.ProposalPOLBitArray.SetIndex(uint(index), true)
- }
- }
-
- func (ps *PeerState) SetHasVote(height uint32, round uint16, type_ uint8, index uint32) {
- ps.mtx.Lock()
- defer ps.mtx.Unlock()
- if ps.Height == height && (ps.Round == round || type_ == VoteTypeCommit) {
- switch type_ {
- case VoteTypeBare:
- ps.Votes.SetIndex(uint(index), true)
- case VoteTypePrecommit:
- ps.Precommits.SetIndex(uint(index), true)
- case VoteTypeCommit:
- ps.Commits.SetIndex(uint(index), true)
- default:
- panic("Invalid vote type")
- }
- }
- }
-
- func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage) error {
- ps.mtx.Lock()
- defer ps.mtx.Unlock()
-
- // Set step state
- 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
-
- // Reset the rest
- ps.Proposal = false
- ps.ProposalBlockHash = nil
- ps.ProposalBlockBitArray = nil
- ps.ProposalPOLHash = nil
- ps.ProposalPOLBitArray = nil
- ps.Votes = nil
- ps.Precommits = nil
- if ps.Height != msg.Height {
- ps.Commits = nil
- }
- return nil
- }
-
- func (ps *PeerState) ApplyHasVotesMessage(msg *HasVotesMessage) error {
- ps.mtx.Lock()
- defer ps.mtx.Unlock()
- if ps.Height == msg.Height {
- ps.Commits = ps.Commits.Or(msg.Commits)
- if ps.Round == msg.Round {
- ps.Votes = ps.Votes.Or(msg.Votes)
- ps.Precommits = ps.Precommits.Or(msg.Precommits)
- } else {
- ps.Votes = msg.Votes
- ps.Precommits = msg.Precommits
- }
- }
- return nil
- }
-
- //-----------------------------------------------------------------------------
- // Messages
-
- const (
- msgTypeUnknown = byte(0x00)
- // Messages for communicating state changes
- msgTypeNewRoundStep = byte(0x01)
- msgTypeHasVotes = byte(0x02)
- // Messages of data
- msgTypeProposal = byte(0x11)
- msgTypePart = byte(0x12) // both block & POL
- msgTypeVote = byte(0x13)
- )
-
- // TODO: check for unnecessary extra bytes at the end.
- func decodeMessage(bz []byte) (msgType byte, msg interface{}) {
- n, err := new(int64), new(error)
- // log.Debug("decoding msg bytes: %X", bz)
- msgType = bz[0]
- r := bytes.NewReader(bz[1:])
- switch msgType {
- // Messages for communicating state changes
- case msgTypeNewRoundStep:
- msg = readNewRoundStepMessage(r, n, err)
- case msgTypeHasVotes:
- msg = readHasVotesMessage(r, n, err)
- // Messages of data
- case msgTypeProposal:
- msg = ReadProposal(r, n, err)
- case msgTypePart:
- msg = readPartMessage(r, n, err)
- case msgTypeVote:
- msg = ReadVote(r, n, err)
- default:
- msg = nil
- }
- return
- }
-
- //-------------------------------------
-
- type NewRoundStepMessage struct {
- Height uint32
- Round uint16
- Step uint8
- SecondsSinceStartTime uint32
- }
-
- func readNewRoundStepMessage(r io.Reader, n *int64, err *error) *NewRoundStepMessage {
- return &NewRoundStepMessage{
- Height: ReadUInt32(r, n, err),
- Round: ReadUInt16(r, n, err),
- Step: ReadUInt8(r, n, err),
- SecondsSinceStartTime: ReadUInt32(r, n, err),
- }
- }
-
- func (m *NewRoundStepMessage) WriteTo(w io.Writer) (n int64, err error) {
- WriteByte(w, msgTypeNewRoundStep, &n, &err)
- WriteUInt32(w, m.Height, &n, &err)
- WriteUInt16(w, m.Round, &n, &err)
- WriteUInt8(w, m.Step, &n, &err)
- WriteUInt32(w, m.SecondsSinceStartTime, &n, &err)
- return
- }
-
- func (m *NewRoundStepMessage) String() string {
- return fmt.Sprintf("[NewRoundStepMessage H:%v R:%v]", m.Height, m.Round)
- }
-
- //-------------------------------------
-
- type HasVotesMessage struct {
- Height uint32
- Round uint16
- Votes BitArray
- Precommits BitArray
- Commits BitArray
- }
-
- func readHasVotesMessage(r io.Reader, n *int64, err *error) *HasVotesMessage {
- return &HasVotesMessage{
- Height: ReadUInt32(r, n, err),
- Round: ReadUInt16(r, n, err),
- Votes: ReadBitArray(r, n, err),
- Precommits: ReadBitArray(r, n, err),
- Commits: ReadBitArray(r, n, err),
- }
- }
-
- func (m *HasVotesMessage) WriteTo(w io.Writer) (n int64, err error) {
- WriteByte(w, msgTypeHasVotes, &n, &err)
- WriteUInt32(w, m.Height, &n, &err)
- WriteUInt16(w, m.Round, &n, &err)
- WriteBinary(w, m.Votes, &n, &err)
- WriteBinary(w, m.Precommits, &n, &err)
- WriteBinary(w, m.Commits, &n, &err)
- return
- }
-
- func (m *HasVotesMessage) String() string {
- return fmt.Sprintf("[HasVotesMessage H:%v R:%v]", m.Height, m.Round)
- }
-
- //-------------------------------------
-
- const (
- partTypeProposalBlock = byte(0x01)
- partTypeProposalPOL = byte(0x02)
- )
-
- type PartMessage struct {
- Height uint32
- Round uint16
- Type byte
- Part *Part
- }
-
- func readPartMessage(r io.Reader, n *int64, err *error) *PartMessage {
- return &PartMessage{
- Height: ReadUInt32(r, n, err),
- Round: ReadUInt16(r, n, err),
- Type: ReadByte(r, n, err),
- Part: ReadPart(r, n, err),
- }
- }
-
- func (m *PartMessage) WriteTo(w io.Writer) (n int64, err error) {
- WriteByte(w, msgTypePart, &n, &err)
- WriteUInt32(w, m.Height, &n, &err)
- WriteUInt16(w, m.Round, &n, &err)
- WriteByte(w, m.Type, &n, &err)
- WriteBinary(w, m.Part, &n, &err)
- return
- }
-
- func (m *PartMessage) String() string {
- return fmt.Sprintf("[PartMessage H:%v R:%v T:%X]", m.Height, m.Round, m.Type)
- }
|