bitbucket.org/number571/tendermint@v0.8.14/internal/consensus/mempool_test.go (about)

     1  package consensus
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"os"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	dbm "github.com/tendermint/tm-db"
    15  
    16  	"bitbucket.org/number571/tendermint/abci/example/code"
    17  	abci "bitbucket.org/number571/tendermint/abci/types"
    18  	mempl "bitbucket.org/number571/tendermint/internal/mempool"
    19  	sm "bitbucket.org/number571/tendermint/state"
    20  	"bitbucket.org/number571/tendermint/store"
    21  	"bitbucket.org/number571/tendermint/types"
    22  )
    23  
    24  // for testing
    25  func assertMempool(txn txNotifier) mempl.Mempool {
    26  	return txn.(mempl.Mempool)
    27  }
    28  
    29  func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) {
    30  	baseConfig := configSetup(t)
    31  
    32  	config := ResetConfig("consensus_mempool_txs_available_test")
    33  	t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) })
    34  
    35  	config.Consensus.CreateEmptyBlocks = false
    36  	state, privVals := randGenesisState(baseConfig, 1, false, 10)
    37  	cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication())
    38  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    39  	height, round := cs.Height, cs.Round
    40  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    41  	startTestRound(cs, height, round)
    42  
    43  	ensureNewEventOnChannel(newBlockCh) // first block gets committed
    44  	ensureNoNewEventOnChannel(newBlockCh)
    45  	deliverTxsRange(cs, 0, 1)
    46  	ensureNewEventOnChannel(newBlockCh) // commit txs
    47  	ensureNewEventOnChannel(newBlockCh) // commit updated app hash
    48  	ensureNoNewEventOnChannel(newBlockCh)
    49  }
    50  
    51  func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) {
    52  	baseConfig := configSetup(t)
    53  
    54  	config := ResetConfig("consensus_mempool_txs_available_test")
    55  	t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) })
    56  
    57  	config.Consensus.CreateEmptyBlocksInterval = ensureTimeout
    58  	state, privVals := randGenesisState(baseConfig, 1, false, 10)
    59  	cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication())
    60  
    61  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    62  
    63  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    64  	startTestRound(cs, cs.Height, cs.Round)
    65  
    66  	ensureNewEventOnChannel(newBlockCh)   // first block gets committed
    67  	ensureNoNewEventOnChannel(newBlockCh) // then we dont make a block ...
    68  	ensureNewEventOnChannel(newBlockCh)   // until the CreateEmptyBlocksInterval has passed
    69  }
    70  
    71  func TestMempoolProgressInHigherRound(t *testing.T) {
    72  	baseConfig := configSetup(t)
    73  
    74  	config := ResetConfig("consensus_mempool_txs_available_test")
    75  	t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) })
    76  
    77  	config.Consensus.CreateEmptyBlocks = false
    78  	state, privVals := randGenesisState(baseConfig, 1, false, 10)
    79  	cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication())
    80  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    81  	height, round := cs.Height, cs.Round
    82  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    83  	newRoundCh := subscribe(cs.eventBus, types.EventQueryNewRound)
    84  	timeoutCh := subscribe(cs.eventBus, types.EventQueryTimeoutPropose)
    85  	cs.setProposal = func(proposal *types.Proposal) error {
    86  		if cs.Height == 2 && cs.Round == 0 {
    87  			// dont set the proposal in round 0 so we timeout and
    88  			// go to next round
    89  			cs.Logger.Info("Ignoring set proposal at height 2, round 0")
    90  			return nil
    91  		}
    92  		return cs.defaultSetProposal(proposal)
    93  	}
    94  	startTestRound(cs, height, round)
    95  
    96  	ensureNewRound(newRoundCh, height, round) // first round at first height
    97  	ensureNewEventOnChannel(newBlockCh)       // first block gets committed
    98  
    99  	height++ // moving to the next height
   100  	round = 0
   101  
   102  	ensureNewRound(newRoundCh, height, round) // first round at next height
   103  	deliverTxsRange(cs, 0, 1)                 // we deliver txs, but dont set a proposal so we get the next round
   104  	ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds())
   105  
   106  	round++                                   // moving to the next round
   107  	ensureNewRound(newRoundCh, height, round) // wait for the next round
   108  	ensureNewEventOnChannel(newBlockCh)       // now we can commit the block
   109  }
   110  
   111  func deliverTxsRange(cs *State, start, end int) {
   112  	// Deliver some txs.
   113  	for i := start; i < end; i++ {
   114  		txBytes := make([]byte, 8)
   115  		binary.BigEndian.PutUint64(txBytes, uint64(i))
   116  		err := assertMempool(cs.txNotifier).CheckTx(context.Background(), txBytes, nil, mempl.TxInfo{})
   117  		if err != nil {
   118  			panic(fmt.Sprintf("Error after CheckTx: %v", err))
   119  		}
   120  	}
   121  }
   122  
   123  func TestMempoolTxConcurrentWithCommit(t *testing.T) {
   124  	config := configSetup(t)
   125  
   126  	state, privVals := randGenesisState(config, 1, false, 10)
   127  	stateStore := sm.NewStore(dbm.NewMemDB())
   128  	blockStore := store.NewBlockStore(dbm.NewMemDB())
   129  	cs := newStateWithConfigAndBlockStore(config, state, privVals[0], NewCounterApplication(), blockStore)
   130  	err := stateStore.Save(state)
   131  	require.NoError(t, err)
   132  	newBlockHeaderCh := subscribe(cs.eventBus, types.EventQueryNewBlockHeader)
   133  
   134  	const numTxs int64 = 3000
   135  	go deliverTxsRange(cs, 0, int(numTxs))
   136  
   137  	startTestRound(cs, cs.Height, cs.Round)
   138  	for n := int64(0); n < numTxs; {
   139  		select {
   140  		case msg := <-newBlockHeaderCh:
   141  			headerEvent := msg.Data().(types.EventDataNewBlockHeader)
   142  			n += headerEvent.NumTxs
   143  		case <-time.After(30 * time.Second):
   144  			t.Fatal("Timed out waiting 30s to commit blocks with transactions")
   145  		}
   146  	}
   147  }
   148  
   149  func TestMempoolRmBadTx(t *testing.T) {
   150  	config := configSetup(t)
   151  
   152  	state, privVals := randGenesisState(config, 1, false, 10)
   153  	app := NewCounterApplication()
   154  	stateStore := sm.NewStore(dbm.NewMemDB())
   155  	blockStore := store.NewBlockStore(dbm.NewMemDB())
   156  	cs := newStateWithConfigAndBlockStore(config, state, privVals[0], app, blockStore)
   157  	err := stateStore.Save(state)
   158  	require.NoError(t, err)
   159  
   160  	// increment the counter by 1
   161  	txBytes := make([]byte, 8)
   162  	binary.BigEndian.PutUint64(txBytes, uint64(0))
   163  
   164  	resDeliver := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
   165  	assert.False(t, resDeliver.IsErr(), fmt.Sprintf("expected no error. got %v", resDeliver))
   166  
   167  	resCommit := app.Commit()
   168  	assert.True(t, len(resCommit.Data) > 0)
   169  
   170  	emptyMempoolCh := make(chan struct{})
   171  	checkTxRespCh := make(chan struct{})
   172  	go func() {
   173  		// Try to send the tx through the mempool.
   174  		// CheckTx should not err, but the app should return a bad abci code
   175  		// and the tx should get removed from the pool
   176  		err := assertMempool(cs.txNotifier).CheckTx(context.Background(), txBytes, func(r *abci.Response) {
   177  			if r.GetCheckTx().Code != code.CodeTypeBadNonce {
   178  				t.Errorf("expected checktx to return bad nonce, got %v", r)
   179  				return
   180  			}
   181  			checkTxRespCh <- struct{}{}
   182  		}, mempl.TxInfo{})
   183  		if err != nil {
   184  			t.Errorf("error after CheckTx: %v", err)
   185  			return
   186  		}
   187  
   188  		// check for the tx
   189  		for {
   190  			txs := assertMempool(cs.txNotifier).ReapMaxBytesMaxGas(int64(len(txBytes)), -1)
   191  			if len(txs) == 0 {
   192  				emptyMempoolCh <- struct{}{}
   193  				return
   194  			}
   195  			time.Sleep(10 * time.Millisecond)
   196  		}
   197  	}()
   198  
   199  	// Wait until the tx returns
   200  	ticker := time.After(time.Second * 5)
   201  	select {
   202  	case <-checkTxRespCh:
   203  		// success
   204  	case <-ticker:
   205  		t.Errorf("timed out waiting for tx to return")
   206  		return
   207  	}
   208  
   209  	// Wait until the tx is removed
   210  	ticker = time.After(time.Second * 5)
   211  	select {
   212  	case <-emptyMempoolCh:
   213  		// success
   214  	case <-ticker:
   215  		t.Errorf("timed out waiting for tx to be removed")
   216  		return
   217  	}
   218  }
   219  
   220  // CounterApplication that maintains a mempool state and resets it upon commit
   221  type CounterApplication struct {
   222  	abci.BaseApplication
   223  
   224  	txCount        int
   225  	mempoolTxCount int
   226  }
   227  
   228  func NewCounterApplication() *CounterApplication {
   229  	return &CounterApplication{}
   230  }
   231  
   232  func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo {
   233  	return abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)}
   234  }
   235  
   236  func (app *CounterApplication) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
   237  	txValue := txAsUint64(req.Tx)
   238  	if txValue != uint64(app.txCount) {
   239  		return abci.ResponseDeliverTx{
   240  			Code: code.CodeTypeBadNonce,
   241  			Log:  fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}
   242  	}
   243  	app.txCount++
   244  	return abci.ResponseDeliverTx{Code: code.CodeTypeOK}
   245  }
   246  
   247  func (app *CounterApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
   248  	txValue := txAsUint64(req.Tx)
   249  	if txValue != uint64(app.mempoolTxCount) {
   250  		return abci.ResponseCheckTx{
   251  			Code: code.CodeTypeBadNonce,
   252  			Log:  fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue)}
   253  	}
   254  	app.mempoolTxCount++
   255  	return abci.ResponseCheckTx{Code: code.CodeTypeOK}
   256  }
   257  
   258  func txAsUint64(tx []byte) uint64 {
   259  	tx8 := make([]byte, 8)
   260  	copy(tx8[len(tx8)-len(tx):], tx)
   261  	return binary.BigEndian.Uint64(tx8)
   262  }
   263  
   264  func (app *CounterApplication) Commit() abci.ResponseCommit {
   265  	app.mempoolTxCount = app.txCount
   266  	if app.txCount == 0 {
   267  		return abci.ResponseCommit{}
   268  	}
   269  	hash := make([]byte, 8)
   270  	binary.BigEndian.PutUint64(hash, uint64(app.txCount))
   271  	return abci.ResponseCommit{Data: hash}
   272  }