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