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