From bc8b3e830b8a128984308b9559592b6fbd07859a Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 28 Jul 2020 15:27:22 +0200 Subject: [PATCH] consensus: added byzantine test, modified previous test (#5150) --- consensus/byzantine_test.go | 172 +++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 3 deletions(-) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 0a3b75867..a20b5260f 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -3,32 +3,190 @@ package consensus import ( "context" "fmt" + "os" + "path" "sync" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tm-db" + + abcicli "github.com/tendermint/tendermint/abci/client" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/evidence" + "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/service" + tmsync "github.com/tendermint/tendermint/libs/sync" + mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/store" "github.com/tendermint/tendermint/types" ) //---------------------------------------------- // byzantine failures +// Byzantine node sends two different prevotes (nil and blockID) to the same validator +func TestByzantinePrevoteEquivocation(t *testing.T) { + const nValidators = 4 + const byzantineNode = 0 + testName := "consensus_byzantine_test" + tickerFunc := newMockTickerFunc(true) + appFunc := newCounter + + genDoc, privVals := randGenesisDoc(nValidators, false, 30) + css := make([]*State, nValidators) + + for i := 0; i < nValidators; i++ { + logger := consensusLogger().With("test", "byzantine", "validator", i) + stateDB := dbm.NewMemDB() // each state needs its own db + state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) + thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) + defer os.RemoveAll(thisConfig.RootDir) + ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal + app := appFunc() + vals := types.TM2PB.ValidatorUpdates(state.Validators) + app.InitChain(abci.RequestInitChain{Validators: vals}) + + blockDB := dbm.NewMemDB() + blockStore := store.NewBlockStore(blockDB) + + // one for mempool, one for consensus + mtx := new(tmsync.Mutex) + proxyAppConnMem := abcicli.NewLocalClient(mtx, app) + proxyAppConnCon := abcicli.NewLocalClient(mtx, app) + + // Make Mempool + mempool := mempl.NewCListMempool(thisConfig.Mempool, proxyAppConnMem, 0) + mempool.SetLogger(log.TestingLogger().With("module", "mempool")) + if thisConfig.Consensus.WaitForTxs() { + mempool.EnableTxsAvailable() + } + + // Make a full instance of the evidence pool + evidenceDB := dbm.NewMemDB() + evpool, err := evidence.NewPool(stateDB, evidenceDB, blockStore) + require.NoError(t, err) + evpool.SetLogger(logger.With("module", "evidence")) + + // Make State + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool) + cs := NewState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) + cs.SetLogger(cs.Logger) + // set private validator + pv := privVals[i] + cs.SetPrivValidator(pv) + + eventBus := types.NewEventBus() + eventBus.SetLogger(log.TestingLogger().With("module", "events")) + eventBus.Start() + cs.SetEventBus(eventBus) + + cs.SetTimeoutTicker(tickerFunc()) + cs.SetLogger(logger) + + css[i] = cs + } + + // initialize the reactors for each of the validators + reactors := make([]*Reactor, nValidators) + blocksSubs := make([]types.Subscription, 0) + eventBuses := make([]*types.EventBus, nValidators) + for i := 0; i < nValidators; i++ { + reactors[i] = NewReactor(css[i], true) // so we dont start the consensus states + reactors[i].SetLogger(css[i].Logger) + + // eventBus is already started with the cs + eventBuses[i] = css[i].eventBus + reactors[i].SetEventBus(eventBuses[i]) + + blocksSub, err := eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock) + require.NoError(t, err) + blocksSubs = append(blocksSubs, blocksSub) + + if css[i].state.LastBlockHeight == 0 { //simulate handle initChain in handshake + sm.SaveState(css[i].blockExec.DB(), css[i].state) + } + } + // make connected switches and start all reactors + p2p.MakeConnectedSwitches(config.P2P, nValidators, func(i int, s *p2p.Switch) *p2p.Switch { + s.AddReactor("CONSENSUS", reactors[i]) + s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) + return s + }, p2p.Connect2Switches) + + // create byzantine validator + bcs := css[byzantineNode] + + // alter prevote so that the byzantine node double votes when height is 2 + bcs.doPrevote = func(height int64, round int32) { + // allow first height to happen normally so that byzantine validator is no longer proposer + if height == 2 { + bcs.Logger.Info("Sending two votes") + prevote1, err := bcs.signVote(tmproto.PrevoteType, bcs.ProposalBlock.Hash(), bcs.ProposalBlockParts.Header()) + require.NoError(t, err) + prevote2, err := bcs.signVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) + require.NoError(t, err) + peerList := reactors[byzantineNode].Switch.Peers().List() + bcs.Logger.Info("Getting peer list", "peers", peerList) + // send two votes to all peers (1st to one half, 2nd to another half) + for i, peer := range peerList { + if i < len(peerList)/2 { + bcs.Logger.Info("Signed and pushed vote", "vote", prevote1, "peer", peer) + peer.Send(VoteChannel, MustEncode(&VoteMessage{prevote1})) + } else { + bcs.Logger.Info("Signed and pushed vote", "vote", prevote2, "peer", peer) + peer.Send(VoteChannel, MustEncode(&VoteMessage{prevote2})) + } + } + } else { + bcs.Logger.Info("Behaving normally") + bcs.defaultDoPrevote(height, round) + } + } + + // start the consensus reactors + for i := 0; i < nValidators; i++ { + s := reactors[i].conS.GetState() + reactors[i].SwitchToConsensus(s, false) + } + defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) + + // Check that evidence is submitted and committed at the third height + for i := 0; i < 2; i++ { + timeoutWaitGroup(t, nValidators, func(j int) { + <-blocksSubs[j].Out() + }, css) + } + timeoutWaitGroup(t, nValidators, func(j int) { + msg := <-blocksSubs[j].Out() + block := msg.Data().(types.EventDataNewBlock).Block + // assert that we have evidence + assert.True(t, len(block.Evidence.Evidence) == 1) + // and that the evidence is of type DuplicateVoteEvidence + ev, ok := block.Evidence.Evidence[0].(*types.DuplicateVoteEvidence) + assert.True(t, ok) + // and that the address matches to that of the byzantine node + pubkey, _ := bcs.privValidator.GetPubKey() + assert.Equal(t, []byte(pubkey.Address()), ev.Address()) + }, css) +} + // 4 validators. 1 is byzantine. The other three are partitioned into A (1 val) and B (2 vals). // byzantine validator sends conflicting proposals into A and B, // and prevotes/precommits on both of them. // B sees a commit, A doesn't. -// Byzantine validator refuses to prevote. // Heal partition and ensure A sees the commit -func TestByzantine(t *testing.T) { +func TestByzantineConflictingProposalsWithPartition(t *testing.T) { N := 4 logger := consensusLogger().With("test", "byzantine") - css, cleanup := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter) + app := newCounter + css, cleanup := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), app) defer cleanup() // give the byzantine validator a normal ticker @@ -52,6 +210,9 @@ func TestByzantine(t *testing.T) { blocksSubs := make([]types.Subscription, N) reactors := make([]p2p.Reactor, N) for i := 0; i < N; i++ { + + // enable txs so we can create different proposals + assertMempool(css[i].txNotifier).EnableTxsAvailable() // make first val byzantine if i == 0 { // NOTE: Now, test validators are MockPV, which by default doesn't @@ -62,6 +223,8 @@ func TestByzantine(t *testing.T) { byzantineDecideProposalFunc(t, height, round, css[j], switches[j]) } }(int32(i)) + // We are setting the prevote function to do nothing because the prevoting + // and precommitting are done alongside the proposal. css[i].doPrevote = func(height int64, round int32) {} } @@ -188,6 +351,9 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int32, cs *St proposal1.Signature = p1.Signature + // some new transactions come in (this ensures that the proposals are different) + deliverTxsRange(cs, 0, 1) + // Create a new proposal block from state/txs from the mempool. block2, blockParts2 := cs.createProposalBlock() polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartSetHeader: blockParts2.Header()}