github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/blockchain/v2/reactor_test.go (about)

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