github.com/Finschia/ostracon@v1.1.5/mempool/v0/clist_mempool_system_test.go (about)

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