func (cs *ConsensusState) newStep() { cs.nSteps += 1 // newStep is called by updateToStep in NewConsensusState before the evsw is set! if cs.evsw != nil { cs.evsw.FireEvent(types.EventStringNewRoundStep(), cs.RoundStateEvent()) } }
// Listens for new steps and votes, // broadcasting the result to peers func (conR *ConsensusReactor) registerEventCallbacks() { conR.evsw.AddListenerForEvent("conR", types.EventStringNewRoundStep(), func(data events.EventData) { rs := data.(*types.EventDataRoundState).RoundState.(*RoundState) conR.broadcastNewRoundStep(rs) }) conR.evsw.AddListenerForEvent("conR", types.EventStringVote(), func(data events.EventData) { edv := data.(*types.EventDataVote) conR.broadcastHasVoteMessage(edv.Vote, edv.Index) }) }
// replay all msgs or start the console func (cs *ConsensusState) replay(file string, console bool) error { if cs.IsRunning() { return errors.New("cs is already running, cannot replay") } if cs.wal != nil { return errors.New("cs wal is open, cannot replay") } cs.startForReplay() // ensure all new step events are regenerated as expected newStepCh := subscribeToEvent(cs.evsw, "replay-test", types.EventStringNewRoundStep(), 1) // just open the file for reading, no need to use wal fp, err := os.OpenFile(file, os.O_RDONLY, 0666) if err != nil { return err } pb := newPlayback(file, fp, cs, cs.state.Copy()) defer pb.fp.Close() var nextN int // apply N msgs in a row for pb.scanner.Scan() { if nextN == 0 && console { nextN = pb.replayConsoleLoop() } if err := pb.cs.readReplayMessage(pb.scanner.Bytes(), newStepCh); err != nil { return err } if nextN > 0 { nextN -= 1 } pb.count += 1 } return nil }
// 4 vals // a polka at round 1 but we miss it // then a polka at round 2 that we lock on // then we see the polka from round 1 but shouldn't unlock func TestLockPOLSafety1(t *testing.T) { cs1, vss := randConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1) voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, 0) <-newRoundCh re := <-proposalCh rs := re.(types.EventDataRoundState).RoundState.(*RoundState) propBlock := rs.ProposalBlock <-voteCh // prevote validatePrevote(t, cs1, 0, vss[0], propBlock.Hash()) // the others sign a polka but we don't see it prevotes := signVoteMany(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet().Header(), cs2, cs3, cs4) // before we time out into new round, set next proposer // and next proposal block /* _, v1 := cs1.Validators.GetByAddress(vss[0].Address) v1.VotingPower = 1 if updated := cs1.Validators.Update(v1); !updated { t.Fatal("failed to update validator") }*/ log.Warn("old prop", "hash", fmt.Sprintf("%X", propBlock.Hash())) // we do see them precommit nil signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) propBlockHash := propBlock.Hash() propBlockParts := propBlock.MakePartSet() incrementRound(cs2, cs3, cs4) //XXX: this isnt gauranteed to get there before the timeoutPropose ... cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer") <-newRoundCh log.Notice("### ONTO ROUND 1") /*Round2 // we timeout and prevote our lock // a polka happened but we didn't see it! */ // now we're on a new round and not the proposer, // but we should receive the proposal select { case re = <-proposalCh: case <-timeoutProposeCh: re = <-proposalCh } rs = re.(types.EventDataRoundState).RoundState.(*RoundState) if rs.LockedBlock != nil { t.Fatal("we should not be locked!") } log.Warn("new prop", "hash", fmt.Sprintf("%X", propBlockHash)) // go to prevote, prevote for proposal block <-voteCh validatePrevote(t, cs1, 1, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) <-voteCh // precommit // we should have precommitted validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash) signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) <-timeoutWaitCh incrementRound(cs2, cs3, cs4) <-newRoundCh log.Notice("### ONTO ROUND 2") /*Round3 we see the polka from round 1 but we shouldn't unlock! */ // timeout of propose <-timeoutProposeCh // finish prevote <-voteCh // we should prevote what we're locked on validatePrevote(t, cs1, 2, vss[0], propBlockHash) newStepCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRoundStep(), 1) // add prevotes from the earlier round addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) log.Warn("Done adding prevotes!") ensureNoNewStep(newStepCh) }
// console function for parsing input and running commands func (pb *playback) replayConsoleLoop() int { for { fmt.Printf("> ") bufReader := bufio.NewReader(os.Stdin) line, more, err := bufReader.ReadLine() if more { Exit("input is too long") } else if err != nil { Exit(err.Error()) } tokens := strings.Split(string(line), " ") if len(tokens) == 0 { continue } switch tokens[0] { case "next": // "next" -> replay next message // "next N" -> replay next N messages if len(tokens) == 1 { return 0 } else { i, err := strconv.Atoi(tokens[1]) if err != nil { fmt.Println("next takes an integer argument") } else { return i } } case "back": // "back" -> go back one message // "back N" -> go back N messages // NOTE: "back" is not supported in the state machine design, // so we restart and replay up to // ensure all new step events are regenerated as expected newStepCh := subscribeToEvent(pb.cs.evsw, "replay-test", types.EventStringNewRoundStep(), 1) if len(tokens) == 1 { pb.replayReset(1, newStepCh) } else { i, err := strconv.Atoi(tokens[1]) if err != nil { fmt.Println("back takes an integer argument") } else if i > pb.count { fmt.Printf("argument to back must not be larger than the current count (%d)\n", pb.count) } else { pb.replayReset(i, newStepCh) } } case "rs": // "rs" -> print entire round state // "rs short" -> print height/round/step // "rs <field>" -> print another field of the round state rs := pb.cs.RoundState if len(tokens) == 1 { fmt.Println(rs) } else { switch tokens[1] { case "short": fmt.Printf("%v/%v/%v\n", rs.Height, rs.Round, rs.Step) case "validators": fmt.Println(rs.Validators) case "proposal": fmt.Println(rs.Proposal) case "proposal_block": fmt.Printf("%v %v\n", rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort()) case "locked_round": fmt.Println(rs.LockedRound) case "locked_block": fmt.Printf("%v %v\n", rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort()) case "votes": fmt.Println(rs.Votes.StringIndented(" ")) default: fmt.Println("Unknown option", tokens[1]) } } case "n": fmt.Println(pb.count) } } return 0 }