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 }