github.com/Finschia/ostracon@v1.1.5/blockchain/v1/reactor_test.go (about)

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