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 }