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