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

     1  package v1
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"math/rand"
     9  	"os"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	"bitbucket.org/number571/tendermint/abci/example/code"
    18  	"bitbucket.org/number571/tendermint/abci/example/kvstore"
    19  	abci "bitbucket.org/number571/tendermint/abci/types"
    20  	"bitbucket.org/number571/tendermint/config"
    21  	"bitbucket.org/number571/tendermint/internal/mempool"
    22  	"bitbucket.org/number571/tendermint/libs/log"
    23  	"bitbucket.org/number571/tendermint/proxy"
    24  	"bitbucket.org/number571/tendermint/types"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  // application extends the KV store application by overriding CheckTx to provide
    29  // transaction priority based on the value in the key/value pair.
    30  type application struct {
    31  	*kvstore.Application
    32  }
    33  
    34  type testTx struct {
    35  	tx       types.Tx
    36  	priority int64
    37  }
    38  
    39  func (app *application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
    40  	var (
    41  		priority int64
    42  		sender   string
    43  	)
    44  
    45  	// infer the priority from the raw transaction value (sender=key=value)
    46  	parts := bytes.Split(req.Tx, []byte("="))
    47  	if len(parts) == 3 {
    48  		v, err := strconv.ParseInt(string(parts[2]), 10, 64)
    49  		if err != nil {
    50  			return abci.ResponseCheckTx{
    51  				Priority:  priority,
    52  				Code:      100,
    53  				GasWanted: 1,
    54  			}
    55  		}
    56  
    57  		priority = v
    58  		sender = string(parts[0])
    59  	} else {
    60  		return abci.ResponseCheckTx{
    61  			Priority:  priority,
    62  			Code:      101,
    63  			GasWanted: 1,
    64  		}
    65  	}
    66  
    67  	return abci.ResponseCheckTx{
    68  		Priority:  priority,
    69  		Sender:    sender,
    70  		Code:      code.CodeTypeOK,
    71  		GasWanted: 1,
    72  	}
    73  }
    74  
    75  func setup(t testing.TB, cacheSize int, options ...TxMempoolOption) *TxMempool {
    76  	t.Helper()
    77  
    78  	app := &application{kvstore.NewApplication()}
    79  	cc := proxy.NewLocalClientCreator(app)
    80  
    81  	cfg := config.ResetTestRoot(strings.ReplaceAll(t.Name(), "/", "|"))
    82  	cfg.Mempool.CacheSize = cacheSize
    83  
    84  	appConnMem, err := cc.NewABCIClient()
    85  	require.NoError(t, err)
    86  	require.NoError(t, appConnMem.Start())
    87  
    88  	t.Cleanup(func() {
    89  		os.RemoveAll(cfg.RootDir)
    90  		require.NoError(t, appConnMem.Stop())
    91  	})
    92  
    93  	return NewTxMempool(log.TestingLogger().With("test", t.Name()), cfg.Mempool, appConnMem, 0, options...)
    94  }
    95  
    96  func checkTxs(t *testing.T, txmp *TxMempool, numTxs int, peerID uint16) []testTx {
    97  	txs := make([]testTx, numTxs)
    98  	txInfo := mempool.TxInfo{SenderID: peerID}
    99  
   100  	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
   101  
   102  	for i := 0; i < numTxs; i++ {
   103  		prefix := make([]byte, 20)
   104  		_, err := rng.Read(prefix)
   105  		require.NoError(t, err)
   106  
   107  		priority := int64(rng.Intn(9999-1000) + 1000)
   108  
   109  		txs[i] = testTx{
   110  			tx:       []byte(fmt.Sprintf("sender-%d-%d=%X=%d", i, peerID, prefix, priority)),
   111  			priority: priority,
   112  		}
   113  		require.NoError(t, txmp.CheckTx(context.Background(), txs[i].tx, nil, txInfo))
   114  	}
   115  
   116  	return txs
   117  }
   118  
   119  func TestTxMempool_TxsAvailable(t *testing.T) {
   120  	txmp := setup(t, 0)
   121  	txmp.EnableTxsAvailable()
   122  
   123  	ensureNoTxFire := func() {
   124  		timer := time.NewTimer(500 * time.Millisecond)
   125  		select {
   126  		case <-txmp.TxsAvailable():
   127  			require.Fail(t, "unexpected transactions event")
   128  		case <-timer.C:
   129  		}
   130  	}
   131  
   132  	ensureTxFire := func() {
   133  		timer := time.NewTimer(500 * time.Millisecond)
   134  		select {
   135  		case <-txmp.TxsAvailable():
   136  		case <-timer.C:
   137  			require.Fail(t, "expected transactions event")
   138  		}
   139  	}
   140  
   141  	// ensure no event as we have not executed any transactions yet
   142  	ensureNoTxFire()
   143  
   144  	// Execute CheckTx for some transactions and ensure TxsAvailable only fires
   145  	// once.
   146  	txs := checkTxs(t, txmp, 100, 0)
   147  	ensureTxFire()
   148  	ensureNoTxFire()
   149  
   150  	rawTxs := make([]types.Tx, len(txs))
   151  	for i, tx := range txs {
   152  		rawTxs[i] = tx.tx
   153  	}
   154  
   155  	responses := make([]*abci.ResponseDeliverTx, len(rawTxs[:50]))
   156  	for i := 0; i < len(responses); i++ {
   157  		responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
   158  	}
   159  
   160  	// commit half the transactions and ensure we fire an event
   161  	txmp.Lock()
   162  	require.NoError(t, txmp.Update(1, rawTxs[:50], responses, nil, nil))
   163  	txmp.Unlock()
   164  	ensureTxFire()
   165  	ensureNoTxFire()
   166  
   167  	// Execute CheckTx for more transactions and ensure we do not fire another
   168  	// event as we're still on the same height (1).
   169  	_ = checkTxs(t, txmp, 100, 0)
   170  	ensureNoTxFire()
   171  }
   172  
   173  func TestTxMempool_Size(t *testing.T) {
   174  	txmp := setup(t, 0)
   175  	txs := checkTxs(t, txmp, 100, 0)
   176  	require.Equal(t, len(txs), txmp.Size())
   177  	require.Equal(t, int64(5690), txmp.SizeBytes())
   178  
   179  	rawTxs := make([]types.Tx, len(txs))
   180  	for i, tx := range txs {
   181  		rawTxs[i] = tx.tx
   182  	}
   183  
   184  	responses := make([]*abci.ResponseDeliverTx, len(rawTxs[:50]))
   185  	for i := 0; i < len(responses); i++ {
   186  		responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
   187  	}
   188  
   189  	txmp.Lock()
   190  	require.NoError(t, txmp.Update(1, rawTxs[:50], responses, nil, nil))
   191  	txmp.Unlock()
   192  
   193  	require.Equal(t, len(rawTxs)/2, txmp.Size())
   194  	require.Equal(t, int64(2850), txmp.SizeBytes())
   195  }
   196  
   197  func TestTxMempool_Flush(t *testing.T) {
   198  	txmp := setup(t, 0)
   199  	txs := checkTxs(t, txmp, 100, 0)
   200  	require.Equal(t, len(txs), txmp.Size())
   201  	require.Equal(t, int64(5690), txmp.SizeBytes())
   202  
   203  	rawTxs := make([]types.Tx, len(txs))
   204  	for i, tx := range txs {
   205  		rawTxs[i] = tx.tx
   206  	}
   207  
   208  	responses := make([]*abci.ResponseDeliverTx, len(rawTxs[:50]))
   209  	for i := 0; i < len(responses); i++ {
   210  		responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
   211  	}
   212  
   213  	txmp.Lock()
   214  	require.NoError(t, txmp.Update(1, rawTxs[:50], responses, nil, nil))
   215  	txmp.Unlock()
   216  
   217  	txmp.Flush()
   218  	require.Zero(t, txmp.Size())
   219  	require.Equal(t, int64(0), txmp.SizeBytes())
   220  }
   221  
   222  func TestTxMempool_ReapMaxBytesMaxGas(t *testing.T) {
   223  	txmp := setup(t, 0)
   224  	tTxs := checkTxs(t, txmp, 100, 0) // all txs request 1 gas unit
   225  	require.Equal(t, len(tTxs), txmp.Size())
   226  	require.Equal(t, int64(5690), txmp.SizeBytes())
   227  
   228  	txMap := make(map[[mempool.TxKeySize]byte]testTx)
   229  	priorities := make([]int64, len(tTxs))
   230  	for i, tTx := range tTxs {
   231  		txMap[mempool.TxKey(tTx.tx)] = tTx
   232  		priorities[i] = tTx.priority
   233  	}
   234  
   235  	sort.Slice(priorities, func(i, j int) bool {
   236  		// sort by priority, i.e. decreasing order
   237  		return priorities[i] > priorities[j]
   238  	})
   239  
   240  	ensurePrioritized := func(reapedTxs types.Txs) {
   241  		reapedPriorities := make([]int64, len(reapedTxs))
   242  		for i, rTx := range reapedTxs {
   243  			reapedPriorities[i] = txMap[mempool.TxKey(rTx)].priority
   244  		}
   245  
   246  		require.Equal(t, priorities[:len(reapedPriorities)], reapedPriorities)
   247  	}
   248  
   249  	// reap by gas capacity only
   250  	reapedTxs := txmp.ReapMaxBytesMaxGas(-1, 50)
   251  	ensurePrioritized(reapedTxs)
   252  	require.Equal(t, len(tTxs), txmp.Size())
   253  	require.Equal(t, int64(5690), txmp.SizeBytes())
   254  	require.Len(t, reapedTxs, 50)
   255  
   256  	// reap by transaction bytes only
   257  	reapedTxs = txmp.ReapMaxBytesMaxGas(1000, -1)
   258  	ensurePrioritized(reapedTxs)
   259  	require.Equal(t, len(tTxs), txmp.Size())
   260  	require.Equal(t, int64(5690), txmp.SizeBytes())
   261  	require.GreaterOrEqual(t, len(reapedTxs), 16)
   262  
   263  	// Reap by both transaction bytes and gas, where the size yields 31 reaped
   264  	// transactions and the gas limit reaps 25 transactions.
   265  	reapedTxs = txmp.ReapMaxBytesMaxGas(1500, 30)
   266  	ensurePrioritized(reapedTxs)
   267  	require.Equal(t, len(tTxs), txmp.Size())
   268  	require.Equal(t, int64(5690), txmp.SizeBytes())
   269  	require.Len(t, reapedTxs, 25)
   270  }
   271  
   272  func TestTxMempool_ReapMaxTxs(t *testing.T) {
   273  	txmp := setup(t, 0)
   274  	tTxs := checkTxs(t, txmp, 100, 0)
   275  	require.Equal(t, len(tTxs), txmp.Size())
   276  	require.Equal(t, int64(5690), txmp.SizeBytes())
   277  
   278  	txMap := make(map[[mempool.TxKeySize]byte]testTx)
   279  	priorities := make([]int64, len(tTxs))
   280  	for i, tTx := range tTxs {
   281  		txMap[mempool.TxKey(tTx.tx)] = tTx
   282  		priorities[i] = tTx.priority
   283  	}
   284  
   285  	sort.Slice(priorities, func(i, j int) bool {
   286  		// sort by priority, i.e. decreasing order
   287  		return priorities[i] > priorities[j]
   288  	})
   289  
   290  	ensurePrioritized := func(reapedTxs types.Txs) {
   291  		reapedPriorities := make([]int64, len(reapedTxs))
   292  		for i, rTx := range reapedTxs {
   293  			reapedPriorities[i] = txMap[mempool.TxKey(rTx)].priority
   294  		}
   295  
   296  		require.Equal(t, priorities[:len(reapedPriorities)], reapedPriorities)
   297  	}
   298  
   299  	// reap all transactions
   300  	reapedTxs := txmp.ReapMaxTxs(-1)
   301  	ensurePrioritized(reapedTxs)
   302  	require.Equal(t, len(tTxs), txmp.Size())
   303  	require.Equal(t, int64(5690), txmp.SizeBytes())
   304  	require.Len(t, reapedTxs, len(tTxs))
   305  
   306  	// reap a single transaction
   307  	reapedTxs = txmp.ReapMaxTxs(1)
   308  	ensurePrioritized(reapedTxs)
   309  	require.Equal(t, len(tTxs), txmp.Size())
   310  	require.Equal(t, int64(5690), txmp.SizeBytes())
   311  	require.Len(t, reapedTxs, 1)
   312  
   313  	// reap half of the transactions
   314  	reapedTxs = txmp.ReapMaxTxs(len(tTxs) / 2)
   315  	ensurePrioritized(reapedTxs)
   316  	require.Equal(t, len(tTxs), txmp.Size())
   317  	require.Equal(t, int64(5690), txmp.SizeBytes())
   318  	require.Len(t, reapedTxs, len(tTxs)/2)
   319  }
   320  
   321  func TestTxMempool_CheckTxExceedsMaxSize(t *testing.T) {
   322  	txmp := setup(t, 0)
   323  
   324  	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
   325  	tx := make([]byte, txmp.config.MaxTxBytes+1)
   326  	_, err := rng.Read(tx)
   327  	require.NoError(t, err)
   328  
   329  	require.Error(t, txmp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{SenderID: 0}))
   330  
   331  	tx = make([]byte, txmp.config.MaxTxBytes-1)
   332  	_, err = rng.Read(tx)
   333  	require.NoError(t, err)
   334  
   335  	require.NoError(t, txmp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{SenderID: 0}))
   336  }
   337  
   338  func TestTxMempool_CheckTxSamePeer(t *testing.T) {
   339  	txmp := setup(t, 100)
   340  	peerID := uint16(1)
   341  	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
   342  
   343  	prefix := make([]byte, 20)
   344  	_, err := rng.Read(prefix)
   345  	require.NoError(t, err)
   346  
   347  	tx := []byte(fmt.Sprintf("sender-0=%X=%d", prefix, 50))
   348  
   349  	require.NoError(t, txmp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{SenderID: peerID}))
   350  	require.Error(t, txmp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{SenderID: peerID}))
   351  }
   352  
   353  func TestTxMempool_CheckTxSameSender(t *testing.T) {
   354  	txmp := setup(t, 100)
   355  	peerID := uint16(1)
   356  	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
   357  
   358  	prefix1 := make([]byte, 20)
   359  	_, err := rng.Read(prefix1)
   360  	require.NoError(t, err)
   361  
   362  	prefix2 := make([]byte, 20)
   363  	_, err = rng.Read(prefix2)
   364  	require.NoError(t, err)
   365  
   366  	tx1 := []byte(fmt.Sprintf("sender-0=%X=%d", prefix1, 50))
   367  	tx2 := []byte(fmt.Sprintf("sender-0=%X=%d", prefix2, 50))
   368  
   369  	require.NoError(t, txmp.CheckTx(context.Background(), tx1, nil, mempool.TxInfo{SenderID: peerID}))
   370  	require.Equal(t, 1, txmp.Size())
   371  	require.NoError(t, txmp.CheckTx(context.Background(), tx2, nil, mempool.TxInfo{SenderID: peerID}))
   372  	require.Equal(t, 1, txmp.Size())
   373  }
   374  
   375  func TestTxMempool_ConcurrentTxs(t *testing.T) {
   376  	txmp := setup(t, 100)
   377  	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
   378  	checkTxDone := make(chan struct{})
   379  
   380  	var wg sync.WaitGroup
   381  
   382  	wg.Add(1)
   383  	go func() {
   384  		for i := 0; i < 20; i++ {
   385  			_ = checkTxs(t, txmp, 100, 0)
   386  			dur := rng.Intn(1000-500) + 500
   387  			time.Sleep(time.Duration(dur) * time.Millisecond)
   388  		}
   389  
   390  		wg.Done()
   391  		close(checkTxDone)
   392  	}()
   393  
   394  	wg.Add(1)
   395  	go func() {
   396  		ticker := time.NewTicker(time.Second)
   397  		defer ticker.Stop()
   398  		defer wg.Done()
   399  
   400  		var height int64 = 1
   401  
   402  		for range ticker.C {
   403  			reapedTxs := txmp.ReapMaxTxs(200)
   404  			if len(reapedTxs) > 0 {
   405  				responses := make([]*abci.ResponseDeliverTx, len(reapedTxs))
   406  				for i := 0; i < len(responses); i++ {
   407  					var code uint32
   408  
   409  					if i%10 == 0 {
   410  						code = 100
   411  					} else {
   412  						code = abci.CodeTypeOK
   413  					}
   414  
   415  					responses[i] = &abci.ResponseDeliverTx{Code: code}
   416  				}
   417  
   418  				txmp.Lock()
   419  				require.NoError(t, txmp.Update(height, reapedTxs, responses, nil, nil))
   420  				txmp.Unlock()
   421  
   422  				height++
   423  			} else {
   424  				// only return once we know we finished the CheckTx loop
   425  				select {
   426  				case <-checkTxDone:
   427  					return
   428  				default:
   429  				}
   430  			}
   431  		}
   432  	}()
   433  
   434  	wg.Wait()
   435  	require.Zero(t, txmp.Size())
   436  	require.Zero(t, txmp.SizeBytes())
   437  }
   438  
   439  func TestTxMempool_ExpiredTxs_NumBlocks(t *testing.T) {
   440  	txmp := setup(t, 500)
   441  	txmp.height = 100
   442  	txmp.config.TTLNumBlocks = 10
   443  
   444  	tTxs := checkTxs(t, txmp, 100, 0)
   445  	require.Equal(t, len(tTxs), txmp.Size())
   446  	require.Equal(t, 100, txmp.heightIndex.Size())
   447  
   448  	// reap 5 txs at the next height -- no txs should expire
   449  	reapedTxs := txmp.ReapMaxTxs(5)
   450  	responses := make([]*abci.ResponseDeliverTx, len(reapedTxs))
   451  	for i := 0; i < len(responses); i++ {
   452  		responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
   453  	}
   454  
   455  	txmp.Lock()
   456  	require.NoError(t, txmp.Update(txmp.height+1, reapedTxs, responses, nil, nil))
   457  	txmp.Unlock()
   458  
   459  	require.Equal(t, 95, txmp.Size())
   460  	require.Equal(t, 95, txmp.heightIndex.Size())
   461  
   462  	// check more txs at height 101
   463  	_ = checkTxs(t, txmp, 50, 1)
   464  	require.Equal(t, 145, txmp.Size())
   465  	require.Equal(t, 145, txmp.heightIndex.Size())
   466  
   467  	// Reap 5 txs at a height that would expire all the transactions from before
   468  	// the previous Update (height 100).
   469  	//
   470  	// NOTE: When we reap txs below, we do not know if we're picking txs from the
   471  	// initial CheckTx calls or from the second round of CheckTx calls. Thus, we
   472  	// cannot guarantee that all 95 txs are remaining that should be expired and
   473  	// removed. However, we do know that that at most 95 txs can be expired and
   474  	// removed.
   475  	reapedTxs = txmp.ReapMaxTxs(5)
   476  	responses = make([]*abci.ResponseDeliverTx, len(reapedTxs))
   477  	for i := 0; i < len(responses); i++ {
   478  		responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
   479  	}
   480  
   481  	txmp.Lock()
   482  	require.NoError(t, txmp.Update(txmp.height+10, reapedTxs, responses, nil, nil))
   483  	txmp.Unlock()
   484  
   485  	require.GreaterOrEqual(t, txmp.Size(), 45)
   486  	require.GreaterOrEqual(t, txmp.heightIndex.Size(), 45)
   487  }
   488  
   489  func TestTxMempool_CheckTxPostCheckError(t *testing.T) {
   490  	cases := []struct {
   491  		name string
   492  		err  error
   493  	}{
   494  		{
   495  			name: "error",
   496  			err:  errors.New("test error"),
   497  		},
   498  		{
   499  			name: "no error",
   500  			err:  nil,
   501  		},
   502  	}
   503  	for _, tc := range cases {
   504  		testCase := tc
   505  		t.Run(testCase.name, func(t *testing.T) {
   506  			postCheckFn := func(_ types.Tx, _ *abci.ResponseCheckTx) error {
   507  				return testCase.err
   508  			}
   509  			txmp := setup(t, 0, WithPostCheck(postCheckFn))
   510  			rng := rand.New(rand.NewSource(time.Now().UnixNano()))
   511  			tx := make([]byte, txmp.config.MaxTxBytes-1)
   512  			_, err := rng.Read(tx)
   513  			require.NoError(t, err)
   514  
   515  			callback := func(res *abci.Response) {
   516  				checkTxRes, ok := res.Value.(*abci.Response_CheckTx)
   517  				require.True(t, ok)
   518  				expectedErrString := ""
   519  				if testCase.err != nil {
   520  					expectedErrString = testCase.err.Error()
   521  				}
   522  				require.Equal(t, expectedErrString, checkTxRes.CheckTx.MempoolError)
   523  			}
   524  			require.NoError(t, txmp.CheckTx(context.Background(), tx, callback, mempool.TxInfo{SenderID: 0}))
   525  		})
   526  	}
   527  }