github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/blockchain/reactor_test.go (about)

     1  package blockchain
     2  
     3  import (
     4  	"log/slog"
     5  	"os"
     6  	"sort"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  
    12  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
    13  	"github.com/gnolang/gno/tm2/pkg/bft/appconn"
    14  	cfg "github.com/gnolang/gno/tm2/pkg/bft/config"
    15  	"github.com/gnolang/gno/tm2/pkg/bft/mempool/mock"
    16  	"github.com/gnolang/gno/tm2/pkg/bft/proxy"
    17  	sm "github.com/gnolang/gno/tm2/pkg/bft/state"
    18  	"github.com/gnolang/gno/tm2/pkg/bft/store"
    19  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    20  	tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time"
    21  	"github.com/gnolang/gno/tm2/pkg/db/memdb"
    22  	"github.com/gnolang/gno/tm2/pkg/errors"
    23  	"github.com/gnolang/gno/tm2/pkg/log"
    24  	"github.com/gnolang/gno/tm2/pkg/p2p"
    25  	"github.com/gnolang/gno/tm2/pkg/testutils"
    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     appconn.AppConns
    53  }
    54  
    55  func newBlockchainReactor(logger *slog.Logger, genDoc *types.GenesisDoc, privVals []types.PrivValidator, maxBlockHeight int64) BlockchainReactorPair {
    56  	if len(privVals) != 1 {
    57  		panic("only support one validator")
    58  	}
    59  
    60  	app := &testApp{}
    61  	cc := proxy.NewLocalClientCreator(app)
    62  	proxyApp := appconn.NewAppConns(cc)
    63  	err := proxyApp.Start()
    64  	if err != nil {
    65  		panic(errors.Wrap(err, "error start app"))
    66  	}
    67  
    68  	blockDB := memdb.NewMemDB()
    69  	stateDB := memdb.NewMemDB()
    70  	blockStore := store.NewBlockStore(blockDB)
    71  
    72  	state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
    73  	if err != nil {
    74  		panic(errors.Wrap(err, "error constructing state from genesis file"))
    75  	}
    76  
    77  	// Make the BlockchainReactor itself.
    78  	// NOTE we have to create and commit the blocks first because
    79  	// pool.height is determined from the store.
    80  	fastSync := true
    81  	db := memdb.NewMemDB()
    82  	blockExec := sm.NewBlockExecutor(db, logger, proxyApp.Consensus(), mock.Mempool{})
    83  	sm.SaveState(db, state)
    84  
    85  	// let's add some blocks in
    86  	for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
    87  		lastCommit := types.NewCommit(types.BlockID{}, nil)
    88  		if blockHeight > 1 {
    89  			lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1)
    90  			lastBlock := blockStore.LoadBlock(blockHeight - 1)
    91  
    92  			vote, err := types.MakeVote(lastBlock.Header.Height, lastBlockMeta.BlockID, state.Validators, privVals[0], lastBlock.Header.ChainID)
    93  			if err != nil {
    94  				panic(err)
    95  			}
    96  			voteCommitSig := vote.CommitSig()
    97  			lastCommit = types.NewCommit(lastBlockMeta.BlockID, []*types.CommitSig{voteCommitSig})
    98  		}
    99  
   100  		thisBlock := makeBlock(blockHeight, state, lastCommit)
   101  
   102  		thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
   103  		blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()}
   104  
   105  		state, err = blockExec.ApplyBlock(state, blockID, thisBlock)
   106  		if err != nil {
   107  			panic(errors.Wrap(err, "error apply block"))
   108  		}
   109  
   110  		blockStore.SaveBlock(thisBlock, thisParts, lastCommit)
   111  	}
   112  
   113  	bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync)
   114  	bcReactor.SetLogger(logger.With("module", "blockchain"))
   115  
   116  	return BlockchainReactorPair{bcReactor, proxyApp}
   117  }
   118  
   119  func TestNoBlockResponse(t *testing.T) {
   120  	t.Parallel()
   121  
   122  	config, _ = cfg.ResetTestRoot("blockchain_reactor_test")
   123  	defer os.RemoveAll(config.RootDir)
   124  	genDoc, privVals := randGenesisDoc(1, false, 30)
   125  
   126  	maxBlockHeight := int64(65)
   127  
   128  	reactorPairs := make([]BlockchainReactorPair, 2)
   129  
   130  	reactorPairs[0] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, maxBlockHeight)
   131  	reactorPairs[1] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, 0)
   132  
   133  	p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch {
   134  		s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor)
   135  		return s
   136  	}, p2p.Connect2Switches)
   137  
   138  	defer func() {
   139  		for _, r := range reactorPairs {
   140  			r.reactor.Stop()
   141  			r.app.Stop()
   142  		}
   143  	}()
   144  
   145  	tests := []struct {
   146  		height   int64
   147  		existent bool
   148  	}{
   149  		{maxBlockHeight + 2, false},
   150  		{10, true},
   151  		{1, true},
   152  		{100, false},
   153  	}
   154  
   155  	for {
   156  		if reactorPairs[1].reactor.pool.IsCaughtUp() {
   157  			break
   158  		}
   159  
   160  		time.Sleep(10 * time.Millisecond)
   161  	}
   162  
   163  	assert.Equal(t, maxBlockHeight, reactorPairs[0].reactor.store.Height())
   164  
   165  	for _, tt := range tests {
   166  		block := reactorPairs[1].reactor.store.LoadBlock(tt.height)
   167  		if tt.existent {
   168  			assert.True(t, block != nil)
   169  		} else {
   170  			assert.True(t, block == nil)
   171  		}
   172  	}
   173  }
   174  
   175  // NOTE: This is too hard to test without
   176  // an easy way to add test peer to switch
   177  // or without significant refactoring of the module.
   178  // Alternatively we could actually dial a TCP conn but
   179  // that seems extreme.
   180  func TestFlappyBadBlockStopsPeer(t *testing.T) {
   181  	t.Parallel()
   182  
   183  	testutils.FilterStability(t, testutils.Flappy)
   184  
   185  	config, _ = cfg.ResetTestRoot("blockchain_reactor_test")
   186  	defer os.RemoveAll(config.RootDir)
   187  	genDoc, privVals := randGenesisDoc(1, false, 30)
   188  
   189  	maxBlockHeight := int64(148)
   190  
   191  	otherChain := newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, maxBlockHeight)
   192  	defer func() {
   193  		otherChain.reactor.Stop()
   194  		otherChain.app.Stop()
   195  	}()
   196  
   197  	reactorPairs := make([]BlockchainReactorPair, 4)
   198  
   199  	reactorPairs[0] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, maxBlockHeight)
   200  	reactorPairs[1] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0)
   201  	reactorPairs[2] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0)
   202  	reactorPairs[3] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0)
   203  
   204  	switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch {
   205  		s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor)
   206  		return s
   207  	}, p2p.Connect2Switches)
   208  
   209  	defer func() {
   210  		for _, r := range reactorPairs {
   211  			r.reactor.Stop()
   212  			r.app.Stop()
   213  		}
   214  	}()
   215  
   216  	for {
   217  		if reactorPairs[3].reactor.pool.IsCaughtUp() {
   218  			break
   219  		}
   220  
   221  		time.Sleep(1 * time.Second)
   222  	}
   223  
   224  	// at this time, reactors[0-3] is the newest
   225  	assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size())
   226  
   227  	// mark reactorPairs[3] is an invalid peer
   228  	reactorPairs[3].reactor.store = otherChain.reactor.store
   229  
   230  	lastReactorPair := newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0)
   231  	reactorPairs = append(reactorPairs, lastReactorPair)
   232  
   233  	switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch {
   234  		s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor)
   235  		return s
   236  	}, p2p.Connect2Switches)...)
   237  
   238  	for i := 0; i < len(reactorPairs)-1; i++ {
   239  		p2p.Connect2Switches(switches, i, len(reactorPairs)-1)
   240  	}
   241  
   242  	for {
   243  		if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 {
   244  			break
   245  		}
   246  
   247  		time.Sleep(1 * time.Second)
   248  	}
   249  
   250  	assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1)
   251  }
   252  
   253  func TestBcBlockRequestMessageValidateBasic(t *testing.T) {
   254  	t.Parallel()
   255  
   256  	testCases := []struct {
   257  		testName      string
   258  		requestHeight int64
   259  		expectErr     bool
   260  	}{
   261  		{"Valid Request Message", 0, false},
   262  		{"Valid Request Message", 1, false},
   263  		{"Invalid Request Message", -1, true},
   264  	}
   265  
   266  	for _, tc := range testCases {
   267  		tc := tc
   268  		t.Run(tc.testName, func(t *testing.T) {
   269  			t.Parallel()
   270  
   271  			request := bcBlockRequestMessage{Height: tc.requestHeight}
   272  			assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result")
   273  		})
   274  	}
   275  }
   276  
   277  func TestBcNoBlockResponseMessageValidateBasic(t *testing.T) {
   278  	t.Parallel()
   279  
   280  	testCases := []struct {
   281  		testName          string
   282  		nonResponseHeight int64
   283  		expectErr         bool
   284  	}{
   285  		{"Valid Non-Response Message", 0, false},
   286  		{"Valid Non-Response Message", 1, false},
   287  		{"Invalid Non-Response Message", -1, true},
   288  	}
   289  
   290  	for _, tc := range testCases {
   291  		tc := tc
   292  		t.Run(tc.testName, func(t *testing.T) {
   293  			t.Parallel()
   294  
   295  			nonResponse := bcNoBlockResponseMessage{Height: tc.nonResponseHeight}
   296  			assert.Equal(t, tc.expectErr, nonResponse.ValidateBasic() != nil, "Validate Basic had an unexpected result")
   297  		})
   298  	}
   299  }
   300  
   301  func TestBcStatusRequestMessageValidateBasic(t *testing.T) {
   302  	t.Parallel()
   303  
   304  	testCases := []struct {
   305  		testName      string
   306  		requestHeight int64
   307  		expectErr     bool
   308  	}{
   309  		{"Valid Request Message", 0, false},
   310  		{"Valid Request Message", 1, false},
   311  		{"Invalid Request Message", -1, true},
   312  	}
   313  
   314  	for _, tc := range testCases {
   315  		tc := tc
   316  		t.Run(tc.testName, func(t *testing.T) {
   317  			t.Parallel()
   318  
   319  			request := bcStatusRequestMessage{Height: tc.requestHeight}
   320  			assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result")
   321  		})
   322  	}
   323  }
   324  
   325  func TestBcStatusResponseMessageValidateBasic(t *testing.T) {
   326  	t.Parallel()
   327  
   328  	testCases := []struct {
   329  		testName       string
   330  		responseHeight int64
   331  		expectErr      bool
   332  	}{
   333  		{"Valid Response Message", 0, false},
   334  		{"Valid Response Message", 1, false},
   335  		{"Invalid Response Message", -1, true},
   336  	}
   337  
   338  	for _, tc := range testCases {
   339  		tc := tc
   340  		t.Run(tc.testName, func(t *testing.T) {
   341  			t.Parallel()
   342  
   343  			response := bcStatusResponseMessage{Height: tc.responseHeight}
   344  			assert.Equal(t, tc.expectErr, response.ValidateBasic() != nil, "Validate Basic had an unexpected result")
   345  		})
   346  	}
   347  }
   348  
   349  // ----------------------------------------------
   350  // utility funcs
   351  
   352  func makeTxs(height int64) (txs []types.Tx) {
   353  	for i := 0; i < 10; i++ {
   354  		txs = append(txs, types.Tx([]byte{byte(height), byte(i)}))
   355  	}
   356  	return txs
   357  }
   358  
   359  func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block {
   360  	block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, state.Validators.GetProposer().Address)
   361  	return block
   362  }
   363  
   364  type testApp struct {
   365  	abci.BaseApplication
   366  }
   367  
   368  var _ abci.Application = (*testApp)(nil)
   369  
   370  func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) {
   371  	return abci.ResponseInfo{}
   372  }
   373  
   374  func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock {
   375  	return abci.ResponseBeginBlock{}
   376  }
   377  
   378  func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
   379  	return abci.ResponseEndBlock{}
   380  }
   381  
   382  func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
   383  	return abci.ResponseDeliverTx{ResponseBase: abci.ResponseBase{Events: []abci.Event{}}}
   384  }
   385  
   386  func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
   387  	return abci.ResponseCheckTx{}
   388  }
   389  
   390  func (app *testApp) Commit() abci.ResponseCommit {
   391  	return abci.ResponseCommit{}
   392  }
   393  
   394  func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
   395  	return
   396  }