github.com/vipernet-xyz/tm@v0.34.24/blockchain/v0/reactor_test.go (about)

     1  package v0
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sort"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/gogo/protobuf/proto"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	dbm "github.com/tendermint/tm-db"
    15  
    16  	abci "github.com/vipernet-xyz/tm/abci/types"
    17  	cfg "github.com/vipernet-xyz/tm/config"
    18  	"github.com/vipernet-xyz/tm/libs/log"
    19  	"github.com/vipernet-xyz/tm/mempool/mock"
    20  	"github.com/vipernet-xyz/tm/p2p"
    21  	bcproto "github.com/vipernet-xyz/tm/proto/tendermint/blockchain"
    22  	"github.com/vipernet-xyz/tm/proxy"
    23  	sm "github.com/vipernet-xyz/tm/state"
    24  	"github.com/vipernet-xyz/tm/store"
    25  	"github.com/vipernet-xyz/tm/types"
    26  	tmtime "github.com/vipernet-xyz/tm/types/time"
    27  )
    28  
    29  var config *cfg.Config
    30  
    31  func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) {
    32  	validators := make([]types.GenesisValidator, numValidators)
    33  	privValidators := make([]types.PrivValidator, numValidators)
    34  	for i := 0; i < numValidators; i++ {
    35  		val, privVal := types.RandValidator(randPower, minPower)
    36  		validators[i] = types.GenesisValidator{
    37  			PubKey: val.PubKey,
    38  			Power:  val.VotingPower,
    39  		}
    40  		privValidators[i] = privVal
    41  	}
    42  	sort.Sort(types.PrivValidatorsByAddress(privValidators))
    43  
    44  	return &types.GenesisDoc{
    45  		GenesisTime: tmtime.Now(),
    46  		ChainID:     config.ChainID(),
    47  		Validators:  validators,
    48  	}, privValidators
    49  }
    50  
    51  type BlockchainReactorPair struct {
    52  	reactor *BlockchainReactor
    53  	app     proxy.AppConns
    54  }
    55  
    56  func newBlockchainReactor(
    57  	logger log.Logger,
    58  	genDoc *types.GenesisDoc,
    59  	privVals []types.PrivValidator,
    60  	maxBlockHeight int64) BlockchainReactorPair {
    61  	if len(privVals) != 1 {
    62  		panic("only support one validator")
    63  	}
    64  
    65  	app := &testApp{}
    66  	cc := proxy.NewLocalClientCreator(app)
    67  	proxyApp := proxy.NewAppConns(cc)
    68  	err := proxyApp.Start()
    69  	if err != nil {
    70  		panic(fmt.Errorf("error start app: %w", err))
    71  	}
    72  
    73  	blockDB := dbm.NewMemDB()
    74  	stateDB := dbm.NewMemDB()
    75  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
    76  		DiscardABCIResponses: false,
    77  	})
    78  	blockStore := store.NewBlockStore(blockDB)
    79  
    80  	state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc)
    81  	if err != nil {
    82  		panic(fmt.Errorf("error constructing state from genesis file: %w", err))
    83  	}
    84  
    85  	// Make the BlockchainReactor itself.
    86  	// NOTE we have to create and commit the blocks first because
    87  	// pool.height is determined from the store.
    88  	fastSync := true
    89  	db := dbm.NewMemDB()
    90  	stateStore = sm.NewStore(db, sm.StoreOptions{
    91  		DiscardABCIResponses: false,
    92  	})
    93  	blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(),
    94  		mock.Mempool{}, sm.EmptyEvidencePool{})
    95  	if err = stateStore.Save(state); err != nil {
    96  		panic(err)
    97  	}
    98  
    99  	// let's add some blocks in
   100  	for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
   101  		lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil)
   102  		if blockHeight > 1 {
   103  			lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1)
   104  			lastBlock := blockStore.LoadBlock(blockHeight - 1)
   105  
   106  			vote, err := types.MakeVote(
   107  				lastBlock.Header.Height,
   108  				lastBlockMeta.BlockID,
   109  				state.Validators,
   110  				privVals[0],
   111  				lastBlock.Header.ChainID,
   112  				time.Now(),
   113  			)
   114  			if err != nil {
   115  				panic(err)
   116  			}
   117  			lastCommit = types.NewCommit(vote.Height, vote.Round,
   118  				lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()})
   119  		}
   120  
   121  		thisBlock := makeBlock(blockHeight, state, lastCommit)
   122  
   123  		thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
   124  		blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()}
   125  
   126  		state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock)
   127  		if err != nil {
   128  			panic(fmt.Errorf("error apply block: %w", err))
   129  		}
   130  
   131  		blockStore.SaveBlock(thisBlock, thisParts, lastCommit)
   132  	}
   133  
   134  	bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync)
   135  	bcReactor.SetLogger(logger.With("module", "blockchain"))
   136  
   137  	return BlockchainReactorPair{bcReactor, proxyApp}
   138  }
   139  
   140  func TestNoBlockResponse(t *testing.T) {
   141  	config = cfg.ResetTestRoot("blockchain_reactor_test")
   142  	defer os.RemoveAll(config.RootDir)
   143  	genDoc, privVals := randGenesisDoc(1, false, 30)
   144  
   145  	maxBlockHeight := int64(65)
   146  
   147  	reactorPairs := make([]BlockchainReactorPair, 2)
   148  
   149  	reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight)
   150  	reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
   151  
   152  	p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch {
   153  		s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor)
   154  		return s
   155  
   156  	}, p2p.Connect2Switches)
   157  
   158  	defer func() {
   159  		for _, r := range reactorPairs {
   160  			err := r.reactor.Stop()
   161  			require.NoError(t, err)
   162  			err = r.app.Stop()
   163  			require.NoError(t, err)
   164  		}
   165  	}()
   166  
   167  	tests := []struct {
   168  		height   int64
   169  		existent bool
   170  	}{
   171  		{maxBlockHeight + 2, false},
   172  		{10, true},
   173  		{1, true},
   174  		{100, false},
   175  	}
   176  
   177  	for {
   178  		if reactorPairs[1].reactor.pool.IsCaughtUp() {
   179  			break
   180  		}
   181  
   182  		time.Sleep(10 * time.Millisecond)
   183  	}
   184  
   185  	assert.Equal(t, maxBlockHeight, reactorPairs[0].reactor.store.Height())
   186  
   187  	for _, tt := range tests {
   188  		block := reactorPairs[1].reactor.store.LoadBlock(tt.height)
   189  		if tt.existent {
   190  			assert.True(t, block != nil)
   191  		} else {
   192  			assert.True(t, block == nil)
   193  		}
   194  	}
   195  }
   196  
   197  func TestLegacyReactorReceiveBasic(t *testing.T) {
   198  	config = cfg.ResetTestRoot("blockchain_reactor_test")
   199  	defer os.RemoveAll(config.RootDir)
   200  	genDoc, privVals := randGenesisDoc(1, false, 30)
   201  	reactor := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 10).reactor
   202  	peer := p2p.CreateRandomPeer(false)
   203  
   204  	reactor.InitPeer(peer)
   205  	reactor.AddPeer(peer)
   206  	m := &bcproto.StatusRequest{}
   207  	wm := m.Wrap()
   208  	msg, err := proto.Marshal(wm)
   209  	assert.NoError(t, err)
   210  
   211  	assert.NotPanics(t, func() {
   212  		reactor.Receive(BlockchainChannel, peer, msg)
   213  	})
   214  }
   215  
   216  // NOTE: This is too hard to test without
   217  // an easy way to add test peer to switch
   218  // or without significant refactoring of the module.
   219  // Alternatively we could actually dial a TCP conn but
   220  // that seems extreme.
   221  func TestBadBlockStopsPeer(t *testing.T) {
   222  	config = cfg.ResetTestRoot("blockchain_reactor_test")
   223  	defer os.RemoveAll(config.RootDir)
   224  	genDoc, privVals := randGenesisDoc(1, false, 30)
   225  
   226  	maxBlockHeight := int64(148)
   227  
   228  	// Other chain needs a different validator set
   229  	otherGenDoc, otherPrivVals := randGenesisDoc(1, false, 30)
   230  	otherChain := newBlockchainReactor(log.TestingLogger(), otherGenDoc, otherPrivVals, maxBlockHeight)
   231  
   232  	defer func() {
   233  		err := otherChain.reactor.Stop()
   234  		require.Error(t, err)
   235  		err = otherChain.app.Stop()
   236  		require.NoError(t, err)
   237  	}()
   238  
   239  	reactorPairs := make([]BlockchainReactorPair, 4)
   240  
   241  	reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight)
   242  	reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
   243  	reactorPairs[2] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
   244  	reactorPairs[3] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
   245  
   246  	switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch {
   247  		s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor)
   248  		return s
   249  
   250  	}, p2p.Connect2Switches)
   251  
   252  	defer func() {
   253  		for _, r := range reactorPairs {
   254  			err := r.reactor.Stop()
   255  			require.NoError(t, err)
   256  
   257  			err = r.app.Stop()
   258  			require.NoError(t, err)
   259  		}
   260  	}()
   261  
   262  	for {
   263  		time.Sleep(1 * time.Second)
   264  		caughtUp := true
   265  		for _, r := range reactorPairs {
   266  			if !r.reactor.pool.IsCaughtUp() {
   267  				caughtUp = false
   268  			}
   269  		}
   270  		if caughtUp {
   271  			break
   272  		}
   273  	}
   274  
   275  	// at this time, reactors[0-3] is the newest
   276  	assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size())
   277  
   278  	// Mark reactorPairs[3] as an invalid peer. Fiddling with .store without a mutex is a data
   279  	// race, but can't be easily avoided.
   280  	reactorPairs[3].reactor.store = otherChain.reactor.store
   281  
   282  	lastReactorPair := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
   283  	reactorPairs = append(reactorPairs, lastReactorPair)
   284  
   285  	switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch {
   286  		s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor)
   287  		return s
   288  
   289  	}, p2p.Connect2Switches)...)
   290  
   291  	for i := 0; i < len(reactorPairs)-1; i++ {
   292  		p2p.Connect2Switches(switches, i, len(reactorPairs)-1)
   293  	}
   294  
   295  	for {
   296  		if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 {
   297  			break
   298  		}
   299  
   300  		time.Sleep(1 * time.Second)
   301  	}
   302  
   303  	assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1)
   304  }
   305  
   306  //----------------------------------------------
   307  // utility funcs
   308  
   309  func makeTxs(height int64) (txs []types.Tx) {
   310  	for i := 0; i < 10; i++ {
   311  		txs = append(txs, types.Tx([]byte{byte(height), byte(i)}))
   312  	}
   313  	return txs
   314  }
   315  
   316  func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block {
   317  	block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address)
   318  	return block
   319  }
   320  
   321  type testApp struct {
   322  	abci.BaseApplication
   323  }
   324  
   325  var _ abci.Application = (*testApp)(nil)
   326  
   327  func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) {
   328  	return abci.ResponseInfo{}
   329  }
   330  
   331  func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock {
   332  	return abci.ResponseBeginBlock{}
   333  }
   334  
   335  func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
   336  	return abci.ResponseEndBlock{}
   337  }
   338  
   339  func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
   340  	return abci.ResponseDeliverTx{Events: []abci.Event{}}
   341  }
   342  
   343  func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
   344  	return abci.ResponseCheckTx{}
   345  }
   346  
   347  func (app *testApp) Commit() abci.ResponseCommit {
   348  	return abci.ResponseCommit{}
   349  }
   350  
   351  func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
   352  	return
   353  }