github.com/okex/exchain@v1.8.0/libs/tendermint/blockchain/v2/reactor_test.go (about)

     1  package v2
     2  
     3  import (
     4  	"net"
     5  	"os"
     6  	"sort"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	dbm "github.com/okex/exchain/libs/tm-db"
    12  	"github.com/pkg/errors"
    13  	"github.com/stretchr/testify/assert"
    14  
    15  	abci "github.com/okex/exchain/libs/tendermint/abci/types"
    16  	"github.com/okex/exchain/libs/tendermint/behaviour"
    17  	cfg "github.com/okex/exchain/libs/tendermint/config"
    18  	"github.com/okex/exchain/libs/tendermint/libs/log"
    19  	"github.com/okex/exchain/libs/tendermint/libs/service"
    20  	"github.com/okex/exchain/libs/tendermint/mock"
    21  	"github.com/okex/exchain/libs/tendermint/p2p"
    22  	"github.com/okex/exchain/libs/tendermint/p2p/conn"
    23  	"github.com/okex/exchain/libs/tendermint/proxy"
    24  	sm "github.com/okex/exchain/libs/tendermint/state"
    25  	"github.com/okex/exchain/libs/tendermint/store"
    26  	"github.com/okex/exchain/libs/tendermint/types"
    27  	tmtime "github.com/okex/exchain/libs/tendermint/types/time"
    28  )
    29  
    30  type mockPeer struct {
    31  	service.Service
    32  	id p2p.ID
    33  }
    34  
    35  func (mp mockPeer) FlushStop()           {}
    36  func (mp mockPeer) ID() p2p.ID           { return mp.id }
    37  func (mp mockPeer) RemoteIP() net.IP     { return net.IP{} }
    38  func (mp mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.RemoteIP(), Port: 8800} }
    39  
    40  func (mp mockPeer) IsOutbound() bool   { return true }
    41  func (mp mockPeer) IsPersistent() bool { return true }
    42  func (mp mockPeer) CloseConn() error   { return nil }
    43  
    44  func (mp mockPeer) NodeInfo() p2p.NodeInfo {
    45  	return p2p.DefaultNodeInfo{
    46  		DefaultNodeID: "",
    47  		ListenAddr:    "",
    48  	}
    49  }
    50  func (mp mockPeer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} }
    51  func (mp mockPeer) SocketAddr() *p2p.NetAddress   { return &p2p.NetAddress{} }
    52  
    53  func (mp mockPeer) Send(byte, []byte) bool    { return true }
    54  func (mp mockPeer) TrySend(byte, []byte) bool { return true }
    55  
    56  func (mp mockPeer) Set(string, interface{}) {}
    57  func (mp mockPeer) Get(string) interface{}  { return struct{}{} }
    58  
    59  //nolint:unused
    60  type mockBlockStore struct {
    61  	blocks map[int64]*types.Block
    62  }
    63  
    64  func (ml *mockBlockStore) Height() int64 {
    65  	return int64(len(ml.blocks))
    66  }
    67  
    68  func (ml *mockBlockStore) LoadBlock(height int64) *types.Block {
    69  	return ml.blocks[height]
    70  }
    71  
    72  func (ml *mockBlockStore) SaveBlock(block *types.Block, part *types.PartSet, commit *types.Commit) {
    73  	ml.blocks[block.Height] = block
    74  }
    75  
    76  type mockBlockApplier struct {
    77  }
    78  
    79  // XXX: Add whitelist/blacklist?
    80  func (mba *mockBlockApplier) ApplyBlock(
    81  	state sm.State, blockID types.BlockID, block *types.Block) (sm.State, int64, error) {
    82  	state.LastBlockHeight++
    83  	return state, 0, nil
    84  }
    85  
    86  type mockSwitchIo struct {
    87  	mtx                 sync.Mutex
    88  	switchedToConsensus bool
    89  	numStatusResponse   int
    90  	numBlockResponse    int
    91  	numNoBlockResponse  int
    92  }
    93  
    94  func (sio *mockSwitchIo) sendBlockRequest(peerID p2p.ID, height int64) error {
    95  	return nil
    96  }
    97  
    98  func (sio *mockSwitchIo) sendStatusResponse(height int64, peerID p2p.ID) error {
    99  	sio.mtx.Lock()
   100  	defer sio.mtx.Unlock()
   101  	sio.numStatusResponse++
   102  	return nil
   103  }
   104  
   105  func (sio *mockSwitchIo) sendBlockToPeer(block *types.Block, peerID p2p.ID) error {
   106  	sio.mtx.Lock()
   107  	defer sio.mtx.Unlock()
   108  	sio.numBlockResponse++
   109  	return nil
   110  }
   111  
   112  func (sio *mockSwitchIo) sendBlockNotFound(height int64, peerID p2p.ID) error {
   113  	sio.mtx.Lock()
   114  	defer sio.mtx.Unlock()
   115  	sio.numNoBlockResponse++
   116  	return nil
   117  }
   118  
   119  func (sio *mockSwitchIo) trySwitchToConsensus(state sm.State, blocksSynced int) {
   120  	sio.mtx.Lock()
   121  	defer sio.mtx.Unlock()
   122  	sio.switchedToConsensus = true
   123  }
   124  
   125  func (sio *mockSwitchIo) broadcastStatusRequest(base int64, height int64) {
   126  }
   127  
   128  type testReactorParams struct {
   129  	logger      log.Logger
   130  	genDoc      *types.GenesisDoc
   131  	privVals    []types.PrivValidator
   132  	startHeight int64
   133  	bufferSize  int
   134  	mockA       bool
   135  }
   136  
   137  func newTestReactor(p testReactorParams) *BlockchainReactor {
   138  	store, state, _ := newReactorStore(p.genDoc, p.privVals, p.startHeight)
   139  	reporter := behaviour.NewMockReporter()
   140  	var appl blockApplier
   141  
   142  	if p.mockA {
   143  		appl = &mockBlockApplier{}
   144  	} else {
   145  		app := &testApp{}
   146  		cc := proxy.NewLocalClientCreator(app)
   147  		proxyApp := proxy.NewAppConns(cc)
   148  		err := proxyApp.Start()
   149  		if err != nil {
   150  			panic(errors.Wrap(err, "error start app"))
   151  		}
   152  		db := dbm.NewMemDB()
   153  		appl = sm.NewBlockExecutor(db, p.logger, proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{})
   154  		sm.SaveState(db, state)
   155  	}
   156  
   157  	r := newReactor(state, store, reporter, appl, p.bufferSize, true)
   158  	logger := log.TestingLogger()
   159  	r.SetLogger(logger.With("module", "blockchain"))
   160  
   161  	return r
   162  }
   163  
   164  // This test is left here and not deleted to retain the termination cases for
   165  // future improvement in [#4482](https://github.com/tendermint/tendermint/issues/4482).
   166  // func TestReactorTerminationScenarios(t *testing.T) {
   167  
   168  // 	config := cfg.ResetTestRoot("blockchain_reactor_v2_test")
   169  // 	defer os.RemoveAll(config.RootDir)
   170  // 	genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30)
   171  // 	refStore, _, _ := newReactorStore(genDoc, privVals, 20)
   172  
   173  // 	params := testReactorParams{
   174  // 		logger:      log.TestingLogger(),
   175  // 		genDoc:      genDoc,
   176  // 		privVals:    privVals,
   177  // 		startHeight: 10,
   178  // 		bufferSize:  100,
   179  // 		mockA:       true,
   180  // 	}
   181  
   182  // 	type testEvent struct {
   183  // 		evType string
   184  // 		peer   string
   185  // 		height int64
   186  // 	}
   187  
   188  // 	tests := []struct {
   189  // 		name   string
   190  // 		params testReactorParams
   191  // 		msgs   []testEvent
   192  // 	}{
   193  // 		{
   194  // 			name:   "simple termination on max peer height - one peer",
   195  // 			params: params,
   196  // 			msgs: []testEvent{
   197  // 				{evType: "AddPeer", peer: "P1"},
   198  // 				{evType: "ReceiveS", peer: "P1", height: 13},
   199  // 				{evType: "BlockReq"},
   200  // 				{evType: "ReceiveB", peer: "P1", height: 11},
   201  // 				{evType: "BlockReq"},
   202  // 				{evType: "BlockReq"},
   203  // 				{evType: "ReceiveB", peer: "P1", height: 12},
   204  // 				{evType: "Process"},
   205  // 				{evType: "ReceiveB", peer: "P1", height: 13},
   206  // 				{evType: "Process"},
   207  // 			},
   208  // 		},
   209  // 		{
   210  // 			name:   "simple termination on max peer height - two peers",
   211  // 			params: params,
   212  // 			msgs: []testEvent{
   213  // 				{evType: "AddPeer", peer: "P1"},
   214  // 				{evType: "AddPeer", peer: "P2"},
   215  // 				{evType: "ReceiveS", peer: "P1", height: 13},
   216  // 				{evType: "ReceiveS", peer: "P2", height: 15},
   217  // 				{evType: "BlockReq"},
   218  // 				{evType: "BlockReq"},
   219  // 				{evType: "ReceiveB", peer: "P1", height: 11},
   220  // 				{evType: "ReceiveB", peer: "P2", height: 12},
   221  // 				{evType: "Process"},
   222  // 				{evType: "BlockReq"},
   223  // 				{evType: "BlockReq"},
   224  // 				{evType: "ReceiveB", peer: "P1", height: 13},
   225  // 				{evType: "Process"},
   226  // 				{evType: "ReceiveB", peer: "P2", height: 14},
   227  // 				{evType: "Process"},
   228  // 				{evType: "BlockReq"},
   229  // 				{evType: "ReceiveB", peer: "P2", height: 15},
   230  // 				{evType: "Process"},
   231  // 			},
   232  // 		},
   233  // 		{
   234  // 			name:   "termination on max peer height - two peers, noBlock error",
   235  // 			params: params,
   236  // 			msgs: []testEvent{
   237  // 				{evType: "AddPeer", peer: "P1"},
   238  // 				{evType: "AddPeer", peer: "P2"},
   239  // 				{evType: "ReceiveS", peer: "P1", height: 13},
   240  // 				{evType: "ReceiveS", peer: "P2", height: 15},
   241  // 				{evType: "BlockReq"},
   242  // 				{evType: "BlockReq"},
   243  // 				{evType: "ReceiveNB", peer: "P1", height: 11},
   244  // 				{evType: "BlockReq"},
   245  // 				{evType: "ReceiveB", peer: "P2", height: 12},
   246  // 				{evType: "ReceiveB", peer: "P2", height: 11},
   247  // 				{evType: "Process"},
   248  // 				{evType: "BlockReq"},
   249  // 				{evType: "BlockReq"},
   250  // 				{evType: "ReceiveB", peer: "P2", height: 13},
   251  // 				{evType: "Process"},
   252  // 				{evType: "ReceiveB", peer: "P2", height: 14},
   253  // 				{evType: "Process"},
   254  // 				{evType: "BlockReq"},
   255  // 				{evType: "ReceiveB", peer: "P2", height: 15},
   256  // 				{evType: "Process"},
   257  // 			},
   258  // 		},
   259  // 		{
   260  // 			name:   "termination on max peer height - two peers, remove one peer",
   261  // 			params: params,
   262  // 			msgs: []testEvent{
   263  // 				{evType: "AddPeer", peer: "P1"},
   264  // 				{evType: "AddPeer", peer: "P2"},
   265  // 				{evType: "ReceiveS", peer: "P1", height: 13},
   266  // 				{evType: "ReceiveS", peer: "P2", height: 15},
   267  // 				{evType: "BlockReq"},
   268  // 				{evType: "BlockReq"},
   269  // 				{evType: "RemovePeer", peer: "P1"},
   270  // 				{evType: "BlockReq"},
   271  // 				{evType: "ReceiveB", peer: "P2", height: 12},
   272  // 				{evType: "ReceiveB", peer: "P2", height: 11},
   273  // 				{evType: "Process"},
   274  // 				{evType: "BlockReq"},
   275  // 				{evType: "BlockReq"},
   276  // 				{evType: "ReceiveB", peer: "P2", height: 13},
   277  // 				{evType: "Process"},
   278  // 				{evType: "ReceiveB", peer: "P2", height: 14},
   279  // 				{evType: "Process"},
   280  // 				{evType: "BlockReq"},
   281  // 				{evType: "ReceiveB", peer: "P2", height: 15},
   282  // 				{evType: "Process"},
   283  // 			},
   284  // 		},
   285  // 	}
   286  
   287  // 	for _, tt := range tests {
   288  // 		tt := tt
   289  // 		t.Run(tt.name, func(t *testing.T) {
   290  // 			reactor := newTestReactor(params)
   291  // 			reactor.Start()
   292  // 			reactor.reporter = behaviour.NewMockReporter()
   293  // 			mockSwitch := &mockSwitchIo{switchedToConsensus: false}
   294  // 			reactor.io = mockSwitch
   295  // 			// time for go routines to start
   296  // 			time.Sleep(time.Millisecond)
   297  
   298  // 			for _, step := range tt.msgs {
   299  // 				switch step.evType {
   300  // 				case "AddPeer":
   301  // 					reactor.scheduler.send(bcAddNewPeer{peerID: p2p.ID(step.peer)})
   302  // 				case "RemovePeer":
   303  // 					reactor.scheduler.send(bcRemovePeer{peerID: p2p.ID(step.peer)})
   304  // 				case "ReceiveS":
   305  // 					reactor.scheduler.send(bcStatusResponse{
   306  // 						peerID: p2p.ID(step.peer),
   307  // 						height: step.height,
   308  // 						time:   time.Now(),
   309  // 					})
   310  // 				case "ReceiveB":
   311  // 					reactor.scheduler.send(bcBlockResponse{
   312  // 						peerID: p2p.ID(step.peer),
   313  // 						block:  refStore.LoadBlock(step.height),
   314  // 						size:   10,
   315  // 						time:   time.Now(),
   316  // 					})
   317  // 				case "ReceiveNB":
   318  // 					reactor.scheduler.send(bcNoBlockResponse{
   319  // 						peerID: p2p.ID(step.peer),
   320  // 						height: step.height,
   321  // 						time:   time.Now(),
   322  // 					})
   323  // 				case "BlockReq":
   324  // 					reactor.scheduler.send(rTrySchedule{time: time.Now()})
   325  // 				case "Process":
   326  // 					reactor.processor.send(rProcessBlock{})
   327  // 				}
   328  // 				// give time for messages to propagate between routines
   329  // 				time.Sleep(time.Millisecond)
   330  // 			}
   331  
   332  // 			// time for processor to finish and reactor to switch to consensus
   333  // 			time.Sleep(20 * time.Millisecond)
   334  // 			assert.True(t, mockSwitch.hasSwitchedToConsensus())
   335  // 			reactor.Stop()
   336  // 		})
   337  // 	}
   338  // }
   339  
   340  func TestReactorHelperMode(t *testing.T) {
   341  	var (
   342  		channelID = byte(0x40)
   343  	)
   344  
   345  	config := cfg.ResetTestRoot("blockchain_reactor_v2_test")
   346  	defer os.RemoveAll(config.RootDir)
   347  	genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30)
   348  
   349  	params := testReactorParams{
   350  		logger:      log.TestingLogger(),
   351  		genDoc:      genDoc,
   352  		privVals:    privVals,
   353  		startHeight: 20,
   354  		bufferSize:  100,
   355  		mockA:       true,
   356  	}
   357  
   358  	type testEvent struct {
   359  		peer  string
   360  		event interface{}
   361  	}
   362  
   363  	tests := []struct {
   364  		name   string
   365  		params testReactorParams
   366  		msgs   []testEvent
   367  	}{
   368  		{
   369  			name:   "status request",
   370  			params: params,
   371  			msgs: []testEvent{
   372  				{"P1", bcStatusRequestMessage{}},
   373  				{"P1", bcBlockRequestMessage{Height: 13}},
   374  				{"P1", bcBlockRequestMessage{Height: 20}},
   375  				{"P1", bcBlockRequestMessage{Height: 22}},
   376  			},
   377  		},
   378  	}
   379  
   380  	for _, tt := range tests {
   381  		tt := tt
   382  		t.Run(tt.name, func(t *testing.T) {
   383  			reactor := newTestReactor(params)
   384  			reactor.Start()
   385  			mockSwitch := &mockSwitchIo{switchedToConsensus: false}
   386  			reactor.io = mockSwitch
   387  
   388  			for i := 0; i < len(tt.msgs); i++ {
   389  				step := tt.msgs[i]
   390  				switch ev := step.event.(type) {
   391  				case bcStatusRequestMessage:
   392  					old := mockSwitch.numStatusResponse
   393  					reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, cdc.MustMarshalBinaryBare(ev))
   394  					assert.Equal(t, old+1, mockSwitch.numStatusResponse)
   395  				case bcBlockRequestMessage:
   396  					if ev.Height > params.startHeight {
   397  						old := mockSwitch.numNoBlockResponse
   398  						reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, cdc.MustMarshalBinaryBare(ev))
   399  						assert.Equal(t, old+1, mockSwitch.numNoBlockResponse)
   400  					} else {
   401  						old := mockSwitch.numBlockResponse
   402  						reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, cdc.MustMarshalBinaryBare(ev))
   403  						assert.Equal(t, old+1, mockSwitch.numBlockResponse)
   404  					}
   405  				}
   406  			}
   407  			reactor.Stop()
   408  		})
   409  	}
   410  }
   411  
   412  func TestReactorSetSwitchNil(t *testing.T) {
   413  	config := cfg.ResetTestRoot("blockchain_reactor_v2_test")
   414  	defer os.RemoveAll(config.RootDir)
   415  	genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30)
   416  
   417  	reactor := newTestReactor(testReactorParams{
   418  		logger:   log.TestingLogger(),
   419  		genDoc:   genDoc,
   420  		privVals: privVals,
   421  	})
   422  	reactor.SetSwitch(nil)
   423  
   424  	assert.Nil(t, reactor.Switch)
   425  	assert.Nil(t, reactor.io)
   426  }
   427  
   428  //----------------------------------------------
   429  // utility funcs
   430  
   431  func makeTxs(height int64) (txs []types.Tx) {
   432  	for i := 0; i < 10; i++ {
   433  		txs = append(txs, types.Tx([]byte{byte(height), byte(i)}))
   434  	}
   435  	return txs
   436  }
   437  
   438  func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block {
   439  	block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address)
   440  	return block
   441  }
   442  
   443  type testApp struct {
   444  	abci.BaseApplication
   445  }
   446  
   447  func randGenesisDoc(chainID string, numValidators int, randPower bool, minPower int64) (
   448  	*types.GenesisDoc, []types.PrivValidator) {
   449  	validators := make([]types.GenesisValidator, numValidators)
   450  	privValidators := make([]types.PrivValidator, numValidators)
   451  	for i := 0; i < numValidators; i++ {
   452  		val, privVal := types.RandValidator(randPower, minPower)
   453  		validators[i] = types.GenesisValidator{
   454  			PubKey: val.PubKey,
   455  			Power:  val.VotingPower,
   456  		}
   457  		privValidators[i] = privVal
   458  	}
   459  	sort.Sort(types.PrivValidatorsByAddress(privValidators))
   460  
   461  	return &types.GenesisDoc{
   462  		GenesisTime: tmtime.Now(),
   463  		ChainID:     chainID,
   464  		Validators:  validators,
   465  	}, privValidators
   466  }
   467  
   468  // Why are we importing the entire blockExecutor dependency graph here
   469  // when we have the facilities to
   470  func newReactorStore(
   471  	genDoc *types.GenesisDoc,
   472  	privVals []types.PrivValidator,
   473  	maxBlockHeight int64) (*store.BlockStore, sm.State, *sm.BlockExecutor) {
   474  	if len(privVals) != 1 {
   475  		panic("only support one validator")
   476  	}
   477  	app := &testApp{}
   478  	cc := proxy.NewLocalClientCreator(app)
   479  	proxyApp := proxy.NewAppConns(cc)
   480  	err := proxyApp.Start()
   481  	if err != nil {
   482  		panic(errors.Wrap(err, "error start app"))
   483  	}
   484  
   485  	stateDB := dbm.NewMemDB()
   486  	blockStore := store.NewBlockStore(dbm.NewMemDB())
   487  
   488  	state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
   489  	if err != nil {
   490  		panic(errors.Wrap(err, "error constructing state from genesis file"))
   491  	}
   492  
   493  	db := dbm.NewMemDB()
   494  	blockExec := sm.NewBlockExecutor(db, log.TestingLogger(), proxyApp.Consensus(),
   495  		mock.Mempool{}, sm.MockEvidencePool{})
   496  	sm.SaveState(db, state)
   497  
   498  	// add blocks in
   499  	for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
   500  		lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil)
   501  		if blockHeight > 1 {
   502  			lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1)
   503  			lastBlock := blockStore.LoadBlock(blockHeight - 1)
   504  			vote, err := types.MakeVote(
   505  				lastBlock.Header.Height,
   506  				lastBlockMeta.BlockID,
   507  				state.Validators,
   508  				privVals[0],
   509  				lastBlock.Header.ChainID,
   510  				time.Now(),
   511  			)
   512  			if err != nil {
   513  				panic(err)
   514  			}
   515  			lastCommit = types.NewCommit(vote.Height, vote.Round,
   516  				lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()})
   517  		}
   518  
   519  		thisBlock := makeBlock(blockHeight, state, lastCommit)
   520  
   521  		thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
   522  		blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()}
   523  
   524  		state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock)
   525  		if err != nil {
   526  			panic(errors.Wrap(err, "error apply block"))
   527  		}
   528  
   529  		blockStore.SaveBlock(thisBlock, thisParts, lastCommit)
   530  	}
   531  
   532  	return blockStore, state, blockExec
   533  }