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  */