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 }