github.com/devwanda/aphelion-staking@v0.33.9/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/devwanda/aphelion-staking/libs/service"
    13  	"github.com/devwanda/aphelion-staking/p2p"
    14  	sm "github.com/devwanda/aphelion-staking/state"
    15  	"github.com/devwanda/aphelion-staking/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 }