// a non-validator should timeout into the prevote round func TestEnterProposeNoPrivValidator(t *testing.T) { css, _ := simpleConsensusState(1) cs := css[0] cs.SetPrivValidator(nil) timeoutChan := make(chan struct{}) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- struct{}{} }) cs.SetFireable(evsw) // starts a go routine for EnterPropose cs.EnterNewRound(cs.Height, 0, false) // go to prevote <-cs.NewStepCh() // if we're not a validator, EnterPropose should timeout select { case rs := <-cs.NewStepCh(): log.Info(rs.String()) t.Fatal("Expected EnterPropose to timeout") case <-timeoutChan: rs := cs.GetRoundState() if rs.Proposal != nil { t.Error("Expected to make no proposal, since no privValidator") } break } }
// subscribes to an AccCall, runs the vm, returns the exception func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas int64) string { // we need to catch the event from the CALL to check for exceptions evsw := events.NewEventSwitch() evsw.Start() ch := make(chan interface{}) fmt.Printf("subscribe to %x\n", subscribeAddr) evsw.AddListenerForEvent("test", types.EventStringAccCall(subscribeAddr), func(msg types.EventData) { ch <- msg }) evc := events.NewEventCache(evsw) ourVm.SetFireable(evc) go func() { start := time.Now() output, err := ourVm.Call(caller, callee, contractCode, []byte{}, 0, &gas) fmt.Printf("Output: %v Error: %v\n", output, err) fmt.Println("Call took:", time.Since(start)) if err != nil { ch <- err.Error() } evc.Flush() }() msg := <-ch switch ev := msg.(type) { case types.EventDataTx: return ev.Exception case types.EventDataCall: return ev.Exception case string: return ev } return "" }
// nil is proposed, so prevote and precommit nil func TestFullRoundNil(t *testing.T) { css, privVals := simpleConsensusState(1) cs := css[0] cs.newStepCh = make(chan *RoundState) // so it blocks cs.SetPrivValidator(nil) timeoutChan := make(chan struct{}) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- struct{}{} }) cs.SetFireable(evsw) // starts a go routine for EnterPropose cs.EnterNewRound(cs.Height, 0, false) // wait to finish propose (we should time out) <-cs.NewStepCh() cs.SetPrivValidator(privVals[0]) // this might be a race condition (uses the mutex that EnterPropose has just released and EnterPrevote is about to grab) <-timeoutChan // wait to finish prevote <-cs.NewStepCh() // should prevote and precommit nil validatePrevoteAndPrecommit(t, cs, 0, 0, privVals[0], nil, nil, nil) }
// run ExecTx and wait for the Call event on given addr // returns the msg data and an error/exception func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx types.Tx, eventid string) (interface{}, string) { evsw := events.NewEventSwitch() evsw.Start() ch := make(chan interface{}) evsw.AddListenerForEvent("test", eventid, func(msg types.EventData) { ch <- msg }) evc := events.NewEventCache(evsw) go func() { if err := ExecTx(blockCache, tx, true, evc); err != nil { ch <- err.Error() } evc.Flush() }() ticker := time.NewTicker(5 * time.Second) var msg interface{} select { case msg = <-ch: case <-ticker.C: return nil, ExceptionTimeOut } switch ev := msg.(type) { case types.EventDataTx: return ev, ev.Exception case types.EventDataCall: return ev, ev.Exception case string: return nil, ev default: return ev, "" } }
func simpleConsensusState(nValidators int) ([]*ConsensusState, []*types.PrivValidator) { // Get State state, privAccs, privVals := sm.RandGenesisState(10, true, 1000, nValidators, false, 10) _, _ = privAccs, privVals fmt.Println(state.BondedValidators) css := make([]*ConsensusState, nValidators) for i := 0; i < nValidators; i++ { // Get BlockStore blockDB := dbm.NewMemDB() blockStore := bc.NewBlockStore(blockDB) // Make MempoolReactor mempool := mempl.NewMempool(state.Copy()) mempoolReactor := mempl.NewMempoolReactor(mempool) mempoolReactor.SetSwitch(p2p.NewSwitch()) // Make ConsensusReactor cs := NewConsensusState(state, blockStore, mempoolReactor) cs.SetPrivValidator(privVals[i]) evsw := events.NewEventSwitch() cs.SetFireable(evsw) // read off the NewHeightStep <-cs.NewStepCh() css[i] = cs } return css, privVals }
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()) }
func simpleConsensusState(nValidators int) (*ConsensusState, []*validatorStub) { // Get State state, privVals := randGenesisState(nValidators, false, 10) // fmt.Println(state.Validators) vss := make([]*validatorStub, nValidators) // make consensus state for lead validator // Get BlockStore blockDB := dbm.NewMemDB() blockStore := bc.NewBlockStore(blockDB) // one for mempool, one for consensus app := example.NewCounterApplication(false) appCMem := app.Open() appCCon := app.Open() proxyAppCtxMem := proxy.NewLocalAppContext(appCMem) proxyAppCtxCon := proxy.NewLocalAppContext(appCCon) // Make Mempool mempool := mempl.NewMempool(proxyAppCtxMem) // Make ConsensusReactor cs := NewConsensusState(state, proxyAppCtxCon, blockStore, mempool) cs.SetPrivValidator(privVals[0]) evsw := events.NewEventSwitch() cs.SetEventSwitch(evsw) evsw.Start() // start the transition routines // cs.startRoutines() for i := 0; i < nValidators; i++ { vss[i] = NewValidatorStub(privVals[i]) } // since cs1 starts at 1 incrementHeight(vss[1:]...) return cs, vss }
// a validator should not timeout of the prevote round (TODO: unless the block is really big!) func TestEnterPropose(t *testing.T) { css, _ := simpleConsensusState(1) cs := css[0] timeoutChan := make(chan struct{}) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- struct{}{} }) cs.SetFireable(evsw) // starts a go routine for EnterPropose cs.EnterNewRound(cs.Height, 0, false) // go to prevote <-cs.NewStepCh() // if we are a validator, we expect it not to timeout select { case <-cs.NewStepCh(): rs := cs.GetRoundState() // Check that Proposal, ProposalBlock, ProposalBlockParts are set. if rs.Proposal == nil { t.Error("rs.Proposal should be set") } if rs.ProposalBlock == nil { t.Error("rs.ProposalBlock should be set") } if rs.ProposalBlockParts.Total() == 0 { t.Error("rs.ProposalBlockParts should be set") } break case <-timeoutChan: t.Fatal("Expected EnterPropose not to timeout") } }
func NewNode() *Node { // Get BlockStore blockStoreDB := dbm.GetDB("blockstore") blockStore := bc.NewBlockStore(blockStoreDB) // Get State state := getState() // Create two proxyAppCtx connections, // one for the consensus and one for the mempool. proxyAddr := config.GetString("proxy_app") proxyAppCtxMempool := getProxyApp(proxyAddr, state.LastAppHash) proxyAppCtxConsensus := getProxyApp(proxyAddr, state.LastAppHash) // add the chainid to the global config config.Set("chain_id", state.ChainID) // Get PrivValidator privValidatorFile := config.GetString("priv_validator_file") privValidator := types.LoadOrGenPrivValidator(privValidatorFile) // Generate node PrivKey privKey := crypto.GenPrivKeyEd25519() // Make event switch eventSwitch := events.NewEventSwitch() _, err := eventSwitch.Start() if err != nil { Exit(Fmt("Failed to start switch: %v", err)) } // Make BlockchainReactor bcReactor := bc.NewBlockchainReactor(state.Copy(), proxyAppCtxConsensus, blockStore, config.GetBool("fast_sync")) // Make MempoolReactor mempool := mempl.NewMempool(proxyAppCtxMempool) mempoolReactor := mempl.NewMempoolReactor(mempool) // Make ConsensusReactor consensusState := consensus.NewConsensusState(state.Copy(), proxyAppCtxConsensus, blockStore, mempool) consensusReactor := consensus.NewConsensusReactor(consensusState, blockStore, config.GetBool("fast_sync")) if privValidator != nil { consensusReactor.SetPrivValidator(privValidator) } // Make p2p network switch sw := p2p.NewSwitch() sw.AddReactor("MEMPOOL", mempoolReactor) sw.AddReactor("BLOCKCHAIN", bcReactor) sw.AddReactor("CONSENSUS", consensusReactor) // add the event switch to all services // they should all satisfy events.Eventable SetEventSwitch(eventSwitch, bcReactor, mempoolReactor, consensusReactor) // run the profile server profileHost := config.GetString("prof_laddr") if profileHost != "" { go func() { log.Warn("Profile server", "error", http.ListenAndServe(profileHost, nil)) }() } return &Node{ sw: sw, evsw: eventSwitch, blockStore: blockStore, bcReactor: bcReactor, mempoolReactor: mempoolReactor, consensusState: consensusState, consensusReactor: consensusReactor, privValidator: privValidator, genesisDoc: state.GenesisDoc, privKey: privKey, } }
func NewNode() *Node { // Get BlockStore blockStoreDB := dbm.GetDB("blockstore") blockStore := bc.NewBlockStore(blockStoreDB) // Get State stateDB := dbm.GetDB("state") state := sm.LoadState(stateDB) var genDoc *stypes.GenesisDoc if state == nil { genDoc, state = sm.MakeGenesisStateFromFile(stateDB, config.GetString("genesis_file")) state.Save() // write the gendoc to db buf, n, err := new(bytes.Buffer), new(int64), new(error) wire.WriteJSON(genDoc, buf, n, err) stateDB.Set(stypes.GenDocKey, buf.Bytes()) if *err != nil { Exit(Fmt("Unable to write gendoc to db: %v", err)) } } else { genDocBytes := stateDB.Get(stypes.GenDocKey) err := new(error) wire.ReadJSONPtr(&genDoc, genDocBytes, err) if *err != nil { Exit(Fmt("Unable to read gendoc from db: %v", err)) } } // add the chainid to the global config config.Set("chain_id", state.ChainID) // Get PrivValidator privValidatorFile := config.GetString("priv_validator_file") privValidator := types.LoadOrGenPrivValidator(privValidatorFile) // Generate node PrivKey privKey := acm.GenPrivKeyEd25519() // Make event switch eventSwitch := events.NewEventSwitch() _, err := eventSwitch.Start() if err != nil { Exit(Fmt("Failed to start switch: %v", err)) } // Make PEXReactor book := p2p.NewAddrBook(config.GetString("addrbook_file")) pexReactor := p2p.NewPEXReactor(book) // Make BlockchainReactor bcReactor := bc.NewBlockchainReactor(state.Copy(), blockStore, config.GetBool("fast_sync")) // Make MempoolReactor mempool := mempl.NewMempool(state.Copy()) mempoolReactor := mempl.NewMempoolReactor(mempool) // Make ConsensusReactor consensusState := consensus.NewConsensusState(state.Copy(), blockStore, mempoolReactor) consensusReactor := consensus.NewConsensusReactor(consensusState, blockStore, config.GetBool("fast_sync")) if privValidator != nil { consensusReactor.SetPrivValidator(privValidator) } // Make p2p network switch sw := p2p.NewSwitch() sw.AddReactor("PEX", pexReactor) sw.AddReactor("MEMPOOL", mempoolReactor) sw.AddReactor("BLOCKCHAIN", bcReactor) sw.AddReactor("CONSENSUS", consensusReactor) // add the event switch to all services // they should all satisfy events.Eventable SetFireable(eventSwitch, pexReactor, bcReactor, mempoolReactor, consensusReactor) // run the profile server profileHost := config.GetString("prof_laddr") if profileHost != "" { go func() { log.Warn("Profile server", "error", http.ListenAndServe(profileHost, nil)) }() } // set vm log level vm.SetDebug(config.GetBool("vm_log")) return &Node{ sw: sw, evsw: eventSwitch, book: book, blockStore: blockStore, pexReactor: pexReactor, bcReactor: bcReactor, mempoolReactor: mempoolReactor, consensusState: consensusState, consensusReactor: consensusReactor, privValidator: privValidator, genDoc: genDoc, privKey: privKey, } }
// 4 vals. // polka P1 at R1, P2 at R2, and P3 at R3, // we lock on P1 at R1, don't see P2, and unlock using P3 at R3 // then we should make sure we don't lock using P2 func TestLockPOLSafety2(t *testing.T) { css, privVals := simpleConsensusState(4) cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] cs1.newStepCh = make(chan *RoundState) // so it blocks timeoutChan := make(chan *types.EventDataRoundState) voteChan := make(chan *types.EventDataVote) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { vote := data.(*types.EventDataVote) // we only fire for our own votes if bytes.Equal(cs1.privValidator.Address, vote.Address) { voteChan <- vote } }) cs1.SetFireable(evsw) // start round and wait for propose and prevote cs1.EnterNewRound(cs1.Height, 0, false) _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() theBlockHash := cs1.ProposalBlock.Hash() donePrecommit := make(chan struct{}) go func() { <-voteChan <-cs1.NewStepCh() donePrecommit <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) <-donePrecommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) donePrecommitWait := make(chan struct{}) go func() { <-cs1.NewStepCh() donePrecommitWait <- struct{}{} }() // add precommits from the rest signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) <-donePrecommitWait // before we time out into new round, set next proposer // and next proposal block _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) v1.VotingPower = 1 if updated := cs1.Validators.Update(v1); !updated { t.Fatal("failed to update validator") } cs2.decideProposal(cs2.Height, cs2.Round+1) prop, propBlock := cs2.Proposal, cs2.ProposalBlock if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with cs2") } incrementRound(cs2, cs3, cs4) // timeout to new round <-timeoutChan log.Info("### ONTO Round 2") /*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, so wait for timeout _, _ = <-cs1.NewStepCh(), <-timeoutChan // go to prevote, prevote for locked block _, _ = <-voteChan, <-cs1.NewStepCh() validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) // the others sign a polka but we don't see it prevotes := signVoteMany(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet().Header(), cs2, cs3, cs4) // once we see prevotes for the next round we'll skip ahead incrementRound(cs2, cs3, cs4) log.Info("### ONTO Round 3") /*Round3 a polka for nil causes us to unlock */ // these prevotes will send us straight to precommit at the higher round donePrecommit = make(chan struct{}) go func() { select { case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit <-voteChan case <-voteChan: // we went straight to Precommit } <-cs1.NewStepCh() donePrecommit <- struct{}{} }() // now lets add prevotes from everyone else for nil signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) <-donePrecommit // we should have unlocked // NOTE: we don't lock on nil, so LockedRound is still 0 validatePrecommit(t, cs1, 2, 0, privVals[0], nil, nil) donePrecommitWait = make(chan struct{}) go func() { // the votes will bring us to new round right away // we should timeut of it and go to prevote <-cs1.NewStepCh() // set the proposal block to be that which got a polka in R2 cs1.mtx.Lock() cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlock.MakePartSet() cs1.mtx.Unlock() // timeout into prevote, finish prevote _, _, _ = <-timeoutChan, <-voteChan, <-cs1.NewStepCh() donePrecommitWait <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) <-donePrecommitWait log.Info("### ONTO ROUND 4") /*Round4 we see the polka from R2 make sure we don't lock because of it! */ // new round and not proposer // (we already timed out and stepped into prevote) log.Warn("adding prevotes from round 2") addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) log.Warn("Done adding prevotes!") // we should prevote it now validatePrevote(t, cs1, 3, privVals[0], cs1.ProposalBlock.Hash()) // but we shouldn't precommit it precommits := cs1.Votes.Precommits(3) vote := precommits.GetByIndex(0) if vote != nil { t.Fatal("validator precommitted at round 4 based on an old polka") } }
// 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) { css, privVals := simpleConsensusState(4) cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] cs1.newStepCh = make(chan *RoundState) // so it blocks timeoutChan := make(chan *types.EventDataRoundState) voteChan := make(chan *types.EventDataVote) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { vote := data.(*types.EventDataVote) // we only fire for our own votes if bytes.Equal(cs1.privValidator.Address, vote.Address) { voteChan <- vote } }) cs1.SetFireable(evsw) // start round and wait for propose and prevote cs1.EnterNewRound(cs1.Height, 0, false) _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() propBlock := cs1.ProposalBlock validatePrevote(t, cs1, 0, privVals[0], cs1.ProposalBlock.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(privVals[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) cs2.decideProposal(cs2.Height, cs2.Round+1) prop, propBlock := cs2.Proposal, cs2.ProposalBlock if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with cs2") } incrementRound(cs2, cs3, cs4) log.Info("### ONTO ROUND 2") /*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, <-cs1.NewStepCh() // so set proposal cs1.mtx.Lock() propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet() cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts cs1.mtx.Unlock() // and wait for timeout <-timeoutChan if cs1.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 _, _ = <-voteChan, <-cs1.NewStepCh() validatePrevote(t, cs1, 1, privVals[0], propBlockHash) // now we see the others prevote for it, so we should lock on it donePrecommit := make(chan struct{}) go func() { select { case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit <-voteChan case <-voteChan: // we went straight to Precommit } <-cs1.NewStepCh() donePrecommit <- struct{}{} }() // now lets add prevotes from everyone else for nil signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) <-donePrecommit // we should have precommitted validatePrecommit(t, cs1, 1, 1, privVals[0], propBlockHash, propBlockHash) // now we see precommits for nil donePrecommitWait := make(chan struct{}) go func() { // the votes will bring us to new round // we should timeut of it and go to prevote <-cs1.NewStepCh() <-timeoutChan donePrecommitWait <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) <-donePrecommitWait incrementRound(cs2, cs3, cs4) log.Info("### ONTO ROUND 3") /*Round3 we see the polka from round 1 but we shouldn't unlock! */ // timeout of propose _, _ = <-cs1.NewStepCh(), <-timeoutChan // finish prevote _, _ = <-voteChan, <-cs1.NewStepCh() // we should prevote what we're locked on validatePrevote(t, cs1, 2, privVals[0], propBlockHash) // add prevotes from the earlier round addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) log.Warn("Done adding prevotes!") ensureNoNewStep(t, cs1) }
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestLockPOLUnlock(t *testing.T) { css, privVals := simpleConsensusState(4) cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] cs1.newStepCh = make(chan *RoundState) // so it blocks timeoutChan := make(chan *types.EventDataRoundState) voteChan := make(chan *types.EventDataVote) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { vote := data.(*types.EventDataVote) // we only fire for our own votes if bytes.Equal(cs1.privValidator.Address, vote.Address) { voteChan <- vote } }) cs1.SetFireable(evsw) // everything done from perspective of cs1 /* Round1 (cs1, B) // B B B B // B nil B nil eg. didn't see the 2/3 prevotes */ // start round and wait for propose and prevote cs1.EnterNewRound(cs1.Height, 0, false) _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() theBlockHash := cs1.ProposalBlock.Hash() donePrecommit := make(chan struct{}) go func() { <-voteChan <-cs1.NewStepCh() donePrecommit <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) <-donePrecommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) donePrecommitWait := make(chan struct{}) go func() { <-cs1.NewStepCh() donePrecommitWait <- struct{}{} }() // add precommits from the rest signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) <-donePrecommitWait // before we time out into new round, set next proposer // and next proposal block _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) v1.VotingPower = 1 if updated := cs1.Validators.Update(v1); !updated { t.Fatal("failed to update validator") } cs2.decideProposal(cs2.Height, cs2.Round+1) prop, propBlock := cs2.Proposal, cs2.ProposalBlock if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with cs2") } incrementRound(cs2, cs3, cs4) // timeout to new round <-timeoutChan log.Info("#### ONTO ROUND 2") /* Round2 (cs2, C) // B nil nil nil // nil nil nil _ cs1 unlocks! */ // now we're on a new round and not the proposer, <-cs1.NewStepCh() cs1.mtx.Lock() // so set the proposal block cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlock.MakePartSet() lockedBlockHash := cs1.LockedBlock.Hash() cs1.mtx.Unlock() // and wait for timeout <-timeoutChan // go to prevote, prevote for locked block (not proposal) _, _ = <-voteChan, <-cs1.NewStepCh() validatePrevote(t, cs1, 0, privVals[0], lockedBlockHash) donePrecommit = make(chan struct{}) go func() { select { case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit <-voteChan case <-voteChan: // we went straight to Precommit } donePrecommit <- struct{}{} }() // now lets add prevotes from everyone else for the new block signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) <-donePrecommit // we should have unlocked // NOTE: we don't lock on nil, so LockedRound is still 0 validatePrecommit(t, cs1, 1, 0, privVals[0], nil, nil) donePrecommitWait = make(chan struct{}) go func() { // the votes will bring us to new round right away // we should timeout of it _, _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh(), <-timeoutChan donePrecommitWait <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) <-donePrecommitWait }
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestLockPOLRelock(t *testing.T) { css, privVals := simpleConsensusState(4) cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] cs1.newStepCh = make(chan *RoundState) // so it blocks timeoutChan := make(chan *types.EventDataRoundState) voteChan := make(chan *types.EventDataVote) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { timeoutChan <- data.(*types.EventDataRoundState) }) evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { vote := data.(*types.EventDataVote) // we only fire for our own votes if bytes.Equal(cs1.privValidator.Address, vote.Address) { voteChan <- vote } }) cs1.SetFireable(evsw) // everything done from perspective of cs1 /* Round1 (cs1, B) // B B B B// B nil B nil eg. cs2 and cs4 didn't see the 2/3 prevotes */ // start round and wait for propose and prevote cs1.EnterNewRound(cs1.Height, 0, false) _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() theBlockHash := cs1.ProposalBlock.Hash() // wait to finish precommit after prevotes done // we do this in a go routine with another channel since otherwise // we may get deadlock with EnterPrecommit waiting to send on newStepCh and the final // signAddVoteToFrom waiting for the cs.mtx.Lock donePrecommit := make(chan struct{}) go func() { <-voteChan <-cs1.NewStepCh() donePrecommit <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) <-donePrecommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) donePrecommitWait := make(chan struct{}) go func() { // (note we're entering precommit for a second time this round) // but with invalid args. then we EnterPrecommitWait, twice (?) <-cs1.NewStepCh() donePrecommitWait <- struct{}{} }() // add precommits from the rest signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) <-donePrecommitWait // before we time out into new round, set next proposer // and next proposal block _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) v1.VotingPower = 1 if updated := cs1.Validators.Update(v1); !updated { t.Fatal("failed to update validator") } cs2.decideProposal(cs2.Height, cs2.Round+1) prop, propBlock := cs2.Proposal, cs2.ProposalBlock if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with cs2") } incrementRound(cs2, cs3, cs4) // timeout to new round te := <-timeoutChan if te.Step != RoundStepPrecommitWait.String() { t.Fatalf("expected to timeout of precommit into new round. got %v", te.Step) } log.Info("### ONTO ROUND 2") /* Round2 (cs2, C) // B C C C // C C C _) cs1 changes lock! */ // now we're on a new round and not the proposer <-cs1.NewStepCh() cs1.mtx.Lock() // so set the proposal block propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet() cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts cs1.mtx.Unlock() // and wait for timeout te = <-timeoutChan if te.Step != RoundStepPropose.String() { t.Fatalf("expected to timeout of propose. got %v", te.Step) } // go to prevote, prevote for locked block (not proposal), move on _, _ = <-voteChan, <-cs1.NewStepCh() validatePrevote(t, cs1, 0, privVals[0], theBlockHash) donePrecommit = make(chan struct{}) go func() { // we need this go routine because if we go into PrevoteWait it has to pull on newStepCh // before the final vote will get added (because it holds the mutex). select { case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit <-voteChan case <-voteChan: // we went straight to Precommit } donePrecommit <- struct{}{} }() // now lets add prevotes from everyone else for the new block signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) <-donePrecommit // we should have unlocked and locked on the new block validatePrecommit(t, cs1, 1, 1, privVals[0], propBlockHash, propBlockHash) donePrecommitWait = make(chan struct{}) go func() { // (note we're entering precommit for a second time this round) // but with invalid args. then we EnterPrecommitWait, <-cs1.NewStepCh() donePrecommitWait <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrecommit, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3) <-donePrecommitWait <-cs1.NewStepCh() rs := <-cs1.NewStepCh() if rs.Height != 2 { t.Fatal("Expected height to increment") } if hash, _, ok := rs.LastCommit.TwoThirdsMajority(); !ok || !bytes.Equal(hash, propBlockHash) { t.Fatal("Expected block to get committed") } }
// two validators, 4 rounds. // val1 proposes the first 2 rounds, and is locked in the first. // val2 proposes the next two. val1 should precommit nil on all (except first where he locks) func TestLockNoPOL(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) /* Round1 (cs1, B) // B B // B B2 */ // start round and wait for propose and prevote cs1.EnterNewRound(cs1.Height, 0, false) _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from cs2: signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) cs1.mtx.Lock() // XXX: sigh theBlockHash := cs1.ProposalBlock.Hash() cs1.mtx.Unlock() // wait to finish precommit <-cs1.NewStepCh() // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) // we should now be stuck in limbo forever, waiting for more precommits // lets add one for a different block // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round hash := cs1.ProposalBlock.Hash() hash[0] = byte((hash[0] + 1) % 255) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) // (note we're entering precommit for a second time this round) // but with invalid args. then we EnterPrecommitWait, and the timeout to new round _, _ = <-cs1.NewStepCh(), <-timeoutChan log.Info("#### ONTO ROUND 2") /* Round2 (cs1, B) // B B2 */ incrementRound(cs2) // go to prevote <-cs1.NewStepCh() // now we're on a new round and the proposer if cs1.ProposalBlock != cs1.LockedBlock { t.Fatalf("Expected proposal block to be locked block. Got %v, Expected %v", cs1.ProposalBlock, cs1.LockedBlock) } // wait to finish prevote <-cs1.NewStepCh() // we should have prevoted our locked block validatePrevote(t, cs1, 1, privVals[0], cs1.LockedBlock.Hash()) // add a conflicting prevote from the other validator signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) // now we're going to enter prevote again, but with invalid args // and then prevote wait, which should timeout. then wait for precommit _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() // the proposed block should still be locked and our precommit added // we should precommit nil and be locked on the proposal validatePrecommit(t, cs1, 1, 0, privVals[0], nil, theBlockHash) // add conflicting precommit from cs2 // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) // (note we're entering precommit for a second time this round, but with invalid args // then we EnterPrecommitWait and timeout into NewRound _, _ = <-cs1.NewStepCh(), <-timeoutChan log.Info("#### ONTO ROUND 3") /* Round3 (cs2, _) // B, B2 */ incrementRound(cs2) // now we're on a new round and not the proposer, so wait for timeout _, _ = <-cs1.NewStepCh(), <-timeoutChan if cs1.ProposalBlock != nil { t.Fatal("Expected proposal block to be nil") } // go to prevote, prevote for locked block <-cs1.NewStepCh() validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) // TODO: quick fastforward to new round, set proposer signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() validatePrecommit(t, cs1, 2, 0, privVals[0], nil, theBlockHash) // precommit nil but be locked on proposal signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) // NOTE: conflicting precommits at same height <-cs1.NewStepCh() // before we time out into new round, set next proposer // and next proposal block _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) v1.VotingPower = 1 if updated := cs1.Validators.Update(v1); !updated { t.Fatal("failed to update validator") } cs2.decideProposal(cs2.Height, cs2.Round+1) prop, propBlock := cs2.Proposal, cs2.ProposalBlock if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with cs2") } incrementRound(cs2) <-timeoutChan log.Info("#### ONTO ROUND 4") /* Round4 (cs2, C) // B C // B C */ // now we're on a new round and not the proposer <-cs1.NewStepCh() // so set the proposal block cs1.mtx.Lock() cs1.Proposal, cs1.ProposalBlock = prop, propBlock cs1.mtx.Unlock() // and wait for timeout <-timeoutChan // go to prevote, prevote for locked block (not proposal) <-cs1.NewStepCh() validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() validatePrecommit(t, cs1, 2, 0, privVals[0], nil, theBlockHash) // precommit nil but locked on proposal signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) // NOTE: conflicting precommits at same height }
// 4 vals. // we receive a final precommit after going into next round, but others might have gone to commit already! func TestHalt1(t *testing.T) { css, privVals := simpleConsensusState(4) cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] cs1.newStepCh = make(chan *RoundState) // so it blocks timeoutChan := make(chan struct{}) evsw := events.NewEventSwitch() evsw.OnStart() evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { timeoutChan <- struct{}{} }) cs1.SetFireable(evsw) // start round and wait for propose and prevote cs1.EnterNewRound(cs1.Height, 0, false) _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() theBlockHash := cs1.ProposalBlock.Hash() donePrecommit := make(chan struct{}) go func() { <-cs1.NewStepCh() donePrecommit <- struct{}{} }() signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs3, cs4) <-donePrecommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) donePrecommitWait := make(chan struct{}) go func() { <-cs1.NewStepCh() donePrecommitWait <- struct{}{} }() // add precommits from the rest signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, nil, types.PartSetHeader{}) // didnt receive proposal signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) // we receive this later, but cs3 might receive it earlier and with ours will go to commit! precommit4 := signVote(cs4, types.VoteTypePrecommit, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) <-donePrecommitWait incrementRound(cs2, cs3, cs4) // timeout to new round <-timeoutChan log.Info("### ONTO ROUND 2") /*Round2 // we timeout and prevote our lock // a polka happened but we didn't see it! */ // go to prevote, prevote for locked block _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) // now we receive the precommit from the previous round addVoteToFrom(cs1, cs4, precommit4) // receiving that precommit should take us straight to commit ensureNewStep(t, cs1) log.Warn("done enter commit!") // update to state ensureNewStep(t, cs1) if cs1.Height != 2 { t.Fatal("expected height to increment") } }
// Tests logs and events. func TestLog4(t *testing.T) { st := newAppState() // Create accounts account1 := &Account{ Address: LeftPadWord256(makeBytes(20)), } account2 := &Account{ Address: LeftPadWord256(makeBytes(20)), } st.accounts[account1.Address.String()] = account1 st.accounts[account2.Address.String()] = account2 ourVm := NewVM(st, newParams(), Zero256, nil) eventSwitch := events.NewEventSwitch() _, err := eventSwitch.Start() if err != nil { t.Errorf("Failed to start eventSwitch: %v", err) } eventID := types.EventStringLogEvent(account2.Address.Postfix(20)) doneChan := make(chan struct{}, 1) eventSwitch.AddListenerForEvent("test", eventID, func(event types.EventData) { logEvent := event.(types.EventDataLog) // No need to test address as this event would not happen if it wasn't correct if !reflect.DeepEqual(logEvent.Topics, expectedTopics) { t.Errorf("Event topics are wrong. Got: %v. Expected: %v", logEvent.Topics, expectedTopics) } if !bytes.Equal(logEvent.Data, expectedData) { t.Errorf("Event data is wrong. Got: %s. Expected: %s", logEvent.Data, expectedData) } if logEvent.Height != expectedHeight { t.Errorf("Event block height is wrong. Got: %d. Expected: %d", logEvent.Height, expectedHeight) } doneChan <- struct{}{} }) ourVm.SetFireable(eventSwitch) var gas int64 = 100000 mstore8 := byte(MSTORE8) push1 := byte(PUSH1) log4 := byte(LOG4) stop := byte(STOP) code := []byte{ push1, 16, // data value push1, 0, // memory slot mstore8, push1, 4, // topic 4 push1, 3, // topic 3 push1, 2, // topic 2 push1, 1, // topic 1 push1, 1, // size of data push1, 0, // data starts at this offset log4, stop, } _, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) <-doneChan if err != nil { t.Fatal(err) } }