github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/mempool/clist_mempool_system_test.go (about)

     1  package mempool
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  
    17  	abci "github.com/tendermint/tendermint/abci/types"
    18  	tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
    19  
    20  	"github.com/line/ostracon/abci/example/counter"
    21  	ocabci "github.com/line/ostracon/abci/types"
    22  	"github.com/line/ostracon/config"
    23  	"github.com/line/ostracon/libs/log"
    24  	"github.com/line/ostracon/proxy"
    25  	"github.com/line/ostracon/types"
    26  	"github.com/line/ostracon/version"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func setupCListMempool(ctx context.Context, t testing.TB,
    31  	height int64, size, cacheSize int) *CListMempool {
    32  	t.Helper()
    33  
    34  	var cancel context.CancelFunc
    35  	_, cancel = context.WithCancel(ctx)
    36  
    37  	cfg := config.ResetTestRoot(strings.ReplaceAll(t.Name(), "/", "|"))
    38  	cfg.Mempool = config.DefaultMempoolConfig()
    39  	logLevel, _ := log.AllowLevel("info")
    40  	logger := log.NewFilter(log.NewOCLogger(log.NewSyncWriter(os.Stdout)), logLevel)
    41  
    42  	appConn := proxy.NewAppConns(proxy.NewLocalClientCreator(counter.NewApplication(false)))
    43  	require.NoError(t, appConn.Start())
    44  
    45  	t.Cleanup(func() {
    46  		os.RemoveAll(cfg.RootDir)
    47  		cancel()
    48  		appConn.Stop() // nolint: errcheck // ignore
    49  	})
    50  
    51  	if size > -1 {
    52  		cfg.Mempool.Size = size
    53  	}
    54  	if cacheSize > -1 {
    55  		cfg.Mempool.CacheSize = cacheSize
    56  	}
    57  	mem := NewCListMempool(cfg.Mempool, appConn.Mempool(), height)
    58  	mem.SetLogger(logger)
    59  	return mem
    60  }
    61  
    62  func TestCListMempool_SystemTestWithCacheSizeDefault(t *testing.T) {
    63  	ctx, cancel := context.WithCancel(context.Background())
    64  	defer cancel()
    65  	mem := setupCListMempool(ctx, t, 1, -1, -1) // size=5000, cacheSize=10000
    66  	recvTxCnt := &receiveTxCounter{}
    67  	stop := make(chan struct{}, 1)
    68  	go gossipRoutine(ctx, t, mem, recvTxCnt, stop)
    69  	makeBlocksAndCommits(ctx, t, mem)
    70  	close(stop)
    71  
    72  	// check the inconsistency
    73  	require.Equal(t, mem.txs.Len(), sizeOfSyncMap(&mem.txsMap))
    74  
    75  	expected := int64(0)
    76  	actual := recvTxCnt.threadSafeCopy()
    77  	assert.NotEqual(t, expected, actual.sent, fmt.Sprintf("actual %d", actual.sent))
    78  	assert.NotEqual(t, expected, actual.success, fmt.Sprintf("actual %d", actual.success))
    79  	assert.NotEqual(t, expected, actual.failInMap, fmt.Sprintf("actual %d", actual.failInMap))
    80  	assert.NotEqual(t, expected, actual.failInCache, fmt.Sprintf("actual %d", actual.failInCache))
    81  	assert.Equal(t, expected, actual.failTooLarge)
    82  	assert.NotEqual(t, expected, actual.failIsFull, fmt.Sprintf("actual %d", actual.failIsFull))
    83  	assert.Equal(t, expected, actual.failPreCheck)
    84  	assert.Equal(t, expected, actual.abciFail)
    85  }
    86  
    87  func sizeOfSyncMap(m *sync.Map) int {
    88  	length := 0
    89  	m.Range(func(_, _ interface{}) bool {
    90  		length++
    91  		return true
    92  	})
    93  	return length
    94  }
    95  
    96  func createProposalBlockAndDeliverTxs(
    97  	mem *CListMempool, height int64) (*types.Block, []*abci.ResponseDeliverTx) {
    98  	// mempool.lock/unlock in ReapMaxBytesMaxGasMaxTxs
    99  	txs := mem.ReapMaxBytesMaxGasMaxTxs(mem.config.MaxTxsBytes, 0, int64(mem.config.Size))
   100  	block := types.MakeBlock(height, txs, nil, nil, tmversion.Consensus{
   101  		Block: version.BlockProtocol,
   102  		App:   version.AppProtocol,
   103  	})
   104  	deliverTxResponses := make([]*abci.ResponseDeliverTx, len(block.Txs))
   105  	for i, tx := range block.Txs {
   106  		deliverTxResponses[i] = &abci.ResponseDeliverTx{
   107  			Code: ocabci.CodeTypeOK,
   108  			Data: tx,
   109  		}
   110  	}
   111  	return block, deliverTxResponses
   112  }
   113  
   114  func commitBlock(ctx context.Context, t *testing.T,
   115  	mem *CListMempool, block *types.Block, deliverTxResponses []*abci.ResponseDeliverTx) {
   116  	mem.Lock()
   117  	defer mem.Unlock()
   118  	err := mem.Update(block, deliverTxResponses, nil, nil)
   119  	require.NoError(t, err)
   120  }
   121  
   122  func receiveTx(ctx context.Context, t *testing.T,
   123  	mem *CListMempool, tx []byte, receiveTxCounter *receiveTxCounter) {
   124  	atomic.AddInt64(&receiveTxCounter.sent, 1)
   125  	txInfo := TxInfo{}
   126  	// mempool.lock/unlock in CheckTxAsync
   127  	mem.CheckTxAsync(tx, txInfo,
   128  		func(err error) {
   129  			if err != nil {
   130  				switch err {
   131  				case ErrTxInCache:
   132  					atomic.AddInt64(&receiveTxCounter.failInCache, 1)
   133  				case ErrTxInMap:
   134  					atomic.AddInt64(&receiveTxCounter.failInMap, 1)
   135  				}
   136  				switch err.(type) {
   137  				case ErrTxTooLarge:
   138  					atomic.AddInt64(&receiveTxCounter.failTooLarge, 1)
   139  				case ErrMempoolIsFull:
   140  					atomic.AddInt64(&receiveTxCounter.failIsFull, 1)
   141  				case ErrPreCheck:
   142  					atomic.AddInt64(&receiveTxCounter.failPreCheck, 1)
   143  				}
   144  			}
   145  		},
   146  		func(res *ocabci.Response) {
   147  			resCheckTx := res.GetCheckTx()
   148  			if resCheckTx.Code != ocabci.CodeTypeOK && len(resCheckTx.Log) != 0 {
   149  				atomic.AddInt64(&receiveTxCounter.abciFail, 1)
   150  			} else {
   151  				atomic.AddInt64(&receiveTxCounter.success, 1)
   152  			}
   153  		})
   154  }
   155  
   156  type receiveTxCounter struct {
   157  	sent         int64
   158  	success      int64
   159  	failInMap    int64
   160  	failInCache  int64
   161  	failTooLarge int64
   162  	failIsFull   int64
   163  	failPreCheck int64
   164  	abciFail     int64
   165  }
   166  
   167  func (r *receiveTxCounter) threadSafeCopy() receiveTxCounter {
   168  	return receiveTxCounter{
   169  		sent:         atomic.LoadInt64(&r.sent),
   170  		success:      atomic.LoadInt64(&r.success),
   171  		failInMap:    atomic.LoadInt64(&r.failInMap),
   172  		failInCache:  atomic.LoadInt64(&r.failInCache),
   173  		failTooLarge: atomic.LoadInt64(&r.failTooLarge),
   174  		failIsFull:   atomic.LoadInt64(&r.failIsFull),
   175  		failPreCheck: atomic.LoadInt64(&r.failPreCheck),
   176  		abciFail:     atomic.LoadInt64(&r.abciFail),
   177  	}
   178  }
   179  
   180  func gossipRoutine(ctx context.Context, t *testing.T, mem *CListMempool,
   181  	receiveTxCounter *receiveTxCounter, stop chan struct{}) {
   182  	for i := 0; i < nodeNum; i++ {
   183  		select {
   184  		case <-stop:
   185  			return
   186  		default:
   187  			go receiveRoutine(ctx, t, mem, receiveTxCounter, stop)
   188  		}
   189  	}
   190  }
   191  
   192  func receiveRoutine(ctx context.Context, t *testing.T, mem *CListMempool,
   193  	receiveTxCounter *receiveTxCounter, stop chan struct{}) {
   194  	for {
   195  		select {
   196  		case <-stop:
   197  			return
   198  		default:
   199  			tx := []byte(strconv.Itoa(rand.Intn(mem.config.CacheSize * 2)))
   200  			// mempool.lock/unlock in CheckTxAsync
   201  			receiveTx(ctx, t, mem, tx, receiveTxCounter)
   202  			if receiveTxCounter.sent%2000 == 0 {
   203  				time.Sleep(time.Second) // for avoiding mempool full
   204  			}
   205  		}
   206  	}
   207  }
   208  
   209  func makeBlocksAndCommits(ctx context.Context, t *testing.T, mem *CListMempool) {
   210  	for i := 0; i < blockNum; i++ {
   211  		block, deliverTxResponses := createProposalBlockAndDeliverTxs(mem, int64(i+1))
   212  		time.Sleep(randQuadraticCurveInterval(deliveredTimeMin, deliveredTimeMax, deliveredTimeRadix))
   213  		commitBlock(ctx, t, mem, block, deliverTxResponses)
   214  		time.Sleep(randQuadraticCurveInterval(blockIntervalMin, blockIntervalMax, blockIntervalRadix))
   215  	}
   216  }
   217  
   218  const (
   219  	nodeNum            = 1
   220  	blockNum           = 10
   221  	blockIntervalMin   = 1.0 // second
   222  	blockIntervalMax   = 1.0 // second
   223  	blockIntervalRadix = 0.1
   224  	deliveredTimeMin   = 2.0  // second
   225  	deliveredTimeMax   = 10.0 // second
   226  	deliveredTimeRadix = 0.1
   227  )
   228  
   229  func randQuadraticCurveInterval(min, max, radix float64) time.Duration {
   230  	rand.Seed(time.Now().UnixNano())
   231  	x := rand.Float64()*(max-min) + min
   232  	y := (x * x) * radix
   233  	return time.Duration(y*1000) * time.Millisecond
   234  }