github.com/vipernet-xyz/tendermint-core@v0.32.0/consensus/byzantine_test.go (about) 1 package consensus 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 12 "github.com/tendermint/tendermint/libs/service" 13 "github.com/tendermint/tendermint/p2p" 14 sm "github.com/tendermint/tendermint/state" 15 "github.com/tendermint/tendermint/types" 16 ) 17 18 //---------------------------------------------- 19 // byzantine failures 20 21 // 4 validators. 1 is byzantine. The other three are partitioned into A (1 val) and B (2 vals). 22 // byzantine validator sends conflicting proposals into A and B, 23 // and prevotes/precommits on both of them. 24 // B sees a commit, A doesn't. 25 // Byzantine validator refuses to prevote. 26 // Heal partition and ensure A sees the commit 27 func TestByzantine(t *testing.T) { 28 N := 4 29 logger := consensusLogger().With("test", "byzantine") 30 css, cleanup := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter) 31 defer cleanup() 32 33 // give the byzantine validator a normal ticker 34 ticker := NewTimeoutTicker() 35 ticker.SetLogger(css[0].Logger) 36 css[0].SetTimeoutTicker(ticker) 37 38 switches := make([]*p2p.Switch, N) 39 p2pLogger := logger.With("module", "p2p") 40 for i := 0; i < N; i++ { 41 switches[i] = p2p.MakeSwitch( 42 config.P2P, 43 i, 44 "foo", "1.0.0", 45 func(i int, sw *p2p.Switch) *p2p.Switch { 46 return sw 47 }) 48 switches[i].SetLogger(p2pLogger.With("validator", i)) 49 } 50 51 blocksSubs := make([]types.Subscription, N) 52 reactors := make([]p2p.Reactor, N) 53 for i := 0; i < N; i++ { 54 // make first val byzantine 55 if i == 0 { 56 // NOTE: Now, test validators are MockPV, which by default doesn't 57 // do any safety checks. 58 css[i].privValidator.(types.MockPV).DisableChecks() 59 css[i].decideProposal = func(j int) func(int64, int) { 60 return func(height int64, round int) { 61 byzantineDecideProposalFunc(t, height, round, css[j], switches[j]) 62 } 63 }(i) 64 css[i].doPrevote = func(height int64, round int) {} 65 } 66 67 eventBus := css[i].eventBus 68 eventBus.SetLogger(logger.With("module", "events", "validator", i)) 69 70 var err error 71 blocksSubs[i], err = eventBus.Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock) 72 require.NoError(t, err) 73 74 conR := NewReactor(css[i], true) // so we don't start the consensus states 75 conR.SetLogger(logger.With("validator", i)) 76 conR.SetEventBus(eventBus) 77 78 var conRI p2p.Reactor = conR 79 80 // make first val byzantine 81 if i == 0 { 82 conRI = NewByzantineReactor(conR) 83 } 84 85 reactors[i] = conRI 86 sm.SaveState(css[i].blockExec.DB(), css[i].state) //for save height 1's validators info 87 } 88 89 defer func() { 90 for _, r := range reactors { 91 if rr, ok := r.(*ByzantineReactor); ok { 92 rr.reactor.Switch.Stop() 93 } else { 94 r.(*Reactor).Switch.Stop() 95 } 96 } 97 }() 98 99 p2p.MakeConnectedSwitches(config.P2P, N, func(i int, s *p2p.Switch) *p2p.Switch { 100 // ignore new switch s, we already made ours 101 switches[i].AddReactor("CONSENSUS", reactors[i]) 102 return switches[i] 103 }, func(sws []*p2p.Switch, i, j int) { 104 // the network starts partitioned with globally active adversary 105 if i != 0 { 106 return 107 } 108 p2p.Connect2Switches(sws, i, j) 109 }) 110 111 // start the non-byz state machines. 112 // note these must be started before the byz 113 for i := 1; i < N; i++ { 114 cr := reactors[i].(*Reactor) 115 cr.SwitchToConsensus(cr.conS.GetState(), 0) 116 } 117 118 // start the byzantine state machine 119 byzR := reactors[0].(*ByzantineReactor) 120 s := byzR.reactor.conS.GetState() 121 byzR.reactor.SwitchToConsensus(s, 0) 122 123 // byz proposer sends one block to peers[0] 124 // and the other block to peers[1] and peers[2]. 125 // note peers and switches order don't match. 126 peers := switches[0].Peers().List() 127 128 // partition A 129 ind0 := getSwitchIndex(switches, peers[0]) 130 131 // partition B 132 ind1 := getSwitchIndex(switches, peers[1]) 133 ind2 := getSwitchIndex(switches, peers[2]) 134 p2p.Connect2Switches(switches, ind1, ind2) 135 136 // wait for someone in the big partition (B) to make a block 137 <-blocksSubs[ind2].Out() 138 139 t.Log("A block has been committed. Healing partition") 140 p2p.Connect2Switches(switches, ind0, ind1) 141 p2p.Connect2Switches(switches, ind0, ind2) 142 143 // wait till everyone makes the first new block 144 // (one of them already has) 145 wg := new(sync.WaitGroup) 146 wg.Add(2) 147 for i := 1; i < N-1; i++ { 148 go func(j int) { 149 <-blocksSubs[j].Out() 150 wg.Done() 151 }(i) 152 } 153 154 done := make(chan struct{}) 155 go func() { 156 wg.Wait() 157 close(done) 158 }() 159 160 tick := time.NewTicker(time.Second * 10) 161 select { 162 case <-done: 163 case <-tick.C: 164 for i, reactor := range reactors { 165 t.Log(fmt.Sprintf("Consensus Reactor %v", i)) 166 t.Log(fmt.Sprintf("%v", reactor)) 167 } 168 t.Fatalf("Timed out waiting for all validators to commit first block") 169 } 170 } 171 172 //------------------------------- 173 // byzantine consensus functions 174 175 func byzantineDecideProposalFunc(t *testing.T, height int64, round int, cs *State, sw *p2p.Switch) { 176 // byzantine user should create two proposals and try to split the vote. 177 // Avoid sending on internalMsgQueue and running consensus state. 178 179 // Create a new proposal block from state/txs from the mempool. 180 block1, blockParts1 := cs.createProposalBlock() 181 polRound, propBlockID := cs.ValidRound, types.BlockID{Hash: block1.Hash(), PartsHeader: blockParts1.Header()} 182 proposal1 := types.NewProposal(height, round, polRound, propBlockID) 183 if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal1); err != nil { 184 t.Error(err) 185 } 186 187 // Create a new proposal block from state/txs from the mempool. 188 block2, blockParts2 := cs.createProposalBlock() 189 polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartsHeader: blockParts2.Header()} 190 proposal2 := types.NewProposal(height, round, polRound, propBlockID) 191 if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal2); err != nil { 192 t.Error(err) 193 } 194 195 block1Hash := block1.Hash() 196 block2Hash := block2.Hash() 197 198 // broadcast conflicting proposals/block parts to peers 199 peers := sw.Peers().List() 200 t.Logf("Byzantine: broadcasting conflicting proposals to %d peers", len(peers)) 201 for i, peer := range peers { 202 if i < len(peers)/2 { 203 go sendProposalAndParts(height, round, cs, peer, proposal1, block1Hash, blockParts1) 204 } else { 205 go sendProposalAndParts(height, round, cs, peer, proposal2, block2Hash, blockParts2) 206 } 207 } 208 } 209 210 func sendProposalAndParts( 211 height int64, 212 round int, 213 cs *State, 214 peer p2p.Peer, 215 proposal *types.Proposal, 216 blockHash []byte, 217 parts *types.PartSet, 218 ) { 219 // proposal 220 msg := &ProposalMessage{Proposal: proposal} 221 peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg)) 222 223 // parts 224 for i := 0; i < parts.Total(); i++ { 225 part := parts.GetPart(i) 226 msg := &BlockPartMessage{ 227 Height: height, // This tells peer that this part applies to us. 228 Round: round, // This tells peer that this part applies to us. 229 Part: part, 230 } 231 peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg)) 232 } 233 234 // votes 235 cs.mtx.Lock() 236 prevote, _ := cs.signVote(types.PrevoteType, blockHash, parts.Header()) 237 precommit, _ := cs.signVote(types.PrecommitType, blockHash, parts.Header()) 238 cs.mtx.Unlock() 239 240 peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(&VoteMessage{prevote})) 241 peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(&VoteMessage{precommit})) 242 } 243 244 //---------------------------------------- 245 // byzantine consensus reactor 246 247 type ByzantineReactor struct { 248 service.Service 249 reactor *Reactor 250 } 251 252 func NewByzantineReactor(conR *Reactor) *ByzantineReactor { 253 return &ByzantineReactor{ 254 Service: conR, 255 reactor: conR, 256 } 257 } 258 259 func (br *ByzantineReactor) SetSwitch(s *p2p.Switch) { br.reactor.SetSwitch(s) } 260 func (br *ByzantineReactor) GetChannels() []*p2p.ChannelDescriptor { return br.reactor.GetChannels() } 261 func (br *ByzantineReactor) AddPeer(peer p2p.Peer) { 262 if !br.reactor.IsRunning() { 263 return 264 } 265 266 // Create peerState for peer 267 peerState := NewPeerState(peer).SetLogger(br.reactor.Logger) 268 peer.Set(types.PeerStateKey, peerState) 269 270 // Send our state to peer. 271 // If we're fast_syncing, broadcast a RoundStepMessage later upon SwitchToConsensus(). 272 if !br.reactor.fastSync { 273 br.reactor.sendNewRoundStepMessage(peer) 274 } 275 } 276 func (br *ByzantineReactor) RemovePeer(peer p2p.Peer, reason interface{}) { 277 br.reactor.RemovePeer(peer, reason) 278 } 279 func (br *ByzantineReactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) { 280 br.reactor.Receive(chID, peer, msgBytes) 281 } 282 func (br *ByzantineReactor) InitPeer(peer p2p.Peer) p2p.Peer { return peer }