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