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