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 }