// Decides on the next proposal and sets them onto cs.Proposal* func (cs *ConsensusState) decideProposal(height int, round int) { var block *types.Block var blockParts *types.PartSet // Decide on block if cs.LockedBlock != nil { // If we're locked onto a block, just choose that. block, blockParts = cs.LockedBlock, cs.LockedBlockParts } else { // Create a new proposal block from state/txs from the mempool. block, blockParts = cs.createProposalBlock() } // Make proposal proposal := types.NewProposal(height, round, blockParts.Header(), cs.Votes.POLRound()) err := cs.privValidator.SignProposal(cs.state.ChainID, proposal) if err == nil { log.Notice("Signed and set proposal", "height", height, "round", round, "proposal", proposal) log.Debug(Fmt("Signed and set proposal block: %v", block)) // Set fields cs.Proposal = proposal cs.ProposalBlock = block cs.ProposalBlockParts = blockParts } else { log.Warn("EnterPropose: Error signing proposal", "height", height, "round", round, "error", err) } }
func TestBadProposal(t *testing.T) { css, privVals := simpleConsensusState(2) cs1, cs2 := css[0], css[1] cs1.newStepCh = make(chan *RoundState) // so it blocks timeoutChan := make(chan struct{}) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- struct{}{} }) evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { timeoutChan <- struct{}{} }) cs1.SetFireable(evsw) // make the second validator the proposer propBlock := changeProposer(t, cs1, cs2) // make the block bad by tampering with statehash stateHash := propBlock.StateHash stateHash[0] = byte((stateHash[0] + 1) % 255) propBlock.StateHash = stateHash propBlockParts := propBlock.MakePartSet() proposal := types.NewProposal(cs2.Height, cs2.Round, propBlockParts.Header(), cs2.Votes.POLRound()) if err := cs2.privValidator.SignProposal(cs2.state.ChainID, proposal); err != nil { t.Fatal("failed to sign bad proposal", err) } // start round cs1.EnterNewRound(cs1.Height, 0, false) // now we're on a new round and not the proposer <-cs1.NewStepCh() // so set the proposal block (and fix voting power) cs1.mtx.Lock() cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = proposal, propBlock, propBlockParts fixVotingPower(t, cs1, privVals[1].Address) cs1.mtx.Unlock() // and wait for timeout <-timeoutChan // go to prevote, prevote for nil (proposal is bad) <-cs1.NewStepCh() validatePrevote(t, cs1, 0, privVals[0], nil) // add bad prevote from cs2. we should precommit nil signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() validatePrecommit(t, cs1, 0, 0, privVals[0], nil, nil) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) }
// create proposal block from cs1 but sign it with vs func decideProposal(cs1 *ConsensusState, cs2 *validatorStub, height, round int) (proposal *types.Proposal, block *types.Block) { block, blockParts := cs1.createProposalBlock() if block == nil { // on error panic("error creating proposal block") } // Make proposal proposal = types.NewProposal(height, round, blockParts.Header(), cs1.Votes.POLRound()) if err := cs2.SignProposal(config.GetString("chain_id"), proposal); err != nil { panic(err) } return }
func TestBadProposal(t *testing.T) { cs1, vss := randConsensusState(2) height, round := cs1.Height, cs1.Round cs2 := vss[1] proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) voteCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringVote(), 1) propBlock, _ := cs1.createProposalBlock() //changeProposer(t, cs1, cs2) // make the second validator the proposer by incrementing round round = round + 1 incrementRound(vss[1:]...) // make the block bad by tampering with statehash stateHash := propBlock.AppHash if len(stateHash) == 0 { stateHash = make([]byte, 32) } stateHash[0] = byte((stateHash[0] + 1) % 255) propBlock.AppHash = stateHash propBlockParts := propBlock.MakePartSet() proposal := types.NewProposal(cs2.Height, round, propBlockParts.Header(), -1) if err := cs2.SignProposal(chainID, proposal); err != nil { t.Fatal("failed to sign bad proposal", err) } // set the proposal block cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer") // start the machine startTestRound(cs1, height, round) // wait for proposal <-proposalCh // wait for prevote <-voteCh validatePrevote(t, cs1, round, vss[0], nil) // add bad prevote from cs2 and wait for it signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) <-voteCh // wait for precommit <-voteCh validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) }
func (cs *ConsensusState) decideProposal(height, round int) { var block *types.Block var blockParts *types.PartSet // Decide on block if cs.LockedBlock != nil { // If we're locked onto a block, just choose that. block, blockParts = cs.LockedBlock, cs.LockedBlockParts } else { // Create a new proposal block from state/txs from the mempool. block, blockParts = cs.createProposalBlock() if block == nil { // on error return } } // Make proposal proposal := types.NewProposal(height, round, blockParts.Header(), cs.Votes.POLRound()) err := cs.privValidator.SignProposal(cs.state.ChainID, proposal) if err == nil { // Set fields /* fields set by setProposal and addBlockPart cs.Proposal = proposal cs.ProposalBlock = block cs.ProposalBlockParts = blockParts */ // send proposal and block parts on internal msg queue cs.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""}) for i := 0; i < blockParts.Total(); i++ { part := blockParts.GetPart(i) cs.sendInternalMessage(msgInfo{&BlockPartMessage{cs.Height, cs.Round, part}, ""}) } log.Info("Signed and sent proposal", "height", height, "round", round, "proposal", proposal) log.Debug(Fmt("Signed and sent proposal block: %v", block)) } else { log.Warn("enterPropose: Error signing proposal", "height", height, "round", round, "error", err) } }
// What we want: // dont see P0, lock on P1 at R1, dont unlock using P0 at R2 func TestLockPOLSafety2(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) unlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringUnlock(), 1) voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // the block for R0: gets polkad but we miss it // (even though we signed it, shhh) _, propBlock0 := decideProposal(cs1, vss[0], cs1.Height, cs1.Round) propBlockHash0 := propBlock0.Hash() propBlockParts0 := propBlock0.MakePartSet() // the others sign a polka but we don't see it prevotes := signVoteMany(types.VoteTypePrevote, propBlockHash0, propBlockParts0.Header(), cs2, cs3, cs4) // the block for round 1 prop1, propBlock1 := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) propBlockHash1 := propBlock1.Hash() propBlockParts1 := propBlock1.MakePartSet() incrementRound(cs2, cs3, cs4) cs1.updateRoundStep(0, RoundStepPrecommitWait) log.Notice("### ONTO Round 1") // jump in at round 1 height := cs1.Height startTestRound(cs1, height, 1) <-newRoundCh cs1.SetProposalAndBlock(prop1, propBlock1, propBlockParts1, "some peer") <-proposalCh <-voteCh // prevote signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash1, propBlockParts1.Header(), cs2, cs3, cs4) <-voteCh // precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash1, propBlockHash1) // add precommits from the rest signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, propBlockHash1, propBlockParts1.Header()) incrementRound(cs2, cs3, cs4) // timeout of precommit wait to new round <-timeoutWaitCh // in round 2 we see the polkad block from round 0 newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0) if err := cs3.SignProposal(chainID, newProp); err != nil { t.Fatal(err) } cs1.SetProposalAndBlock(newProp, propBlock0, propBlockParts0, "some peer") addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) // add the pol votes <-newRoundCh log.Notice("### ONTO Round 2") /*Round2 // now we see the polka from round 1, but we shouldnt unlock */ select { case <-timeoutProposeCh: <-proposalCh case <-proposalCh: } select { case <-unlockCh: t.Fatal("validator unlocked using an old polka") case <-voteCh: // prevote our locked block } validatePrevote(t, cs1, 2, vss[0], propBlockHash1) }